Что такое x402?
x402 — это открытый протокол для нативных интернет-платежей. Код ошибки 402 означает «Payment Required» («Требуется оплата») и давно существует в спецификации HTTP, однако лишь теперь его практическое применение стало возможным благодаря развитию блокчейн-сетей. Протокол 402 реализует паттерн HTTP 402 Payment Required: сервер требует оплату перед тем, как вернуть защищённый ответ. На Solana это обычно реализуется путём запроса клиенту на выполнение небольшого перевода, после чего сервер верифицирует его в блокчейне и предоставляет контент.
На данный момент ещё неясно, какой из 402 SDK станет наиболее популярным. В этом руководстве мы покажем, как реализовать x402 с использованием минимального сервера и клиента, а также приведём список всех доступных 402 SDK с описанием текущей поддержки Solana.
Как это работает?
Существует несколько способов реализации x402 — от максимально лёгких до полностью управляемых.
Идея протокола: используется обычный HTTP. Клиент обращается к вашему URL → вы отвечаете 402 Payment Required с JSON-объектом Payment Requirements → клиент совершает оплату и повторяет запрос с заголовком X-PAYMENT → вы верифицируете/проводите расчёт → отвечаете 200 OK. Никаких аккаунтов, никакого OAuth.
Диаграмма потока x402
Примечание: фасилитатор полностью опционален — вы также можете реализовать собственную логику валидации буквально несколькими строками кода. Фасилитатор абстрагирует детали интеграции с блокчейном от сервера и клиента, что упрощает реализацию.
Ключевые элементы спецификации: структура PaymentRequirements, заголовок X-PAYMENT в кодировке base64, опциональный X-PAYMENT-RESPONSE при успехе, а также (опциональный) API фасилитатора для /verify, /settle, /supported. Текущая конкретная схема — exact (оплата фиксированной суммы). Другие схемы, например upto, находятся в стадии предложения.
Поддержка Solana: сам протокол не привязан к конкретной сети; на Solana поддерживаются все SPL-токены. Поддержка Solana доступна или находится в разработке для большинства 402 SDK.
Ниже представлен список доступных 402 SDK с описанием текущей поддержки Solana.
Варианты использования
x402 открывает широкий спектр сценариев микроплатежей и оплаты по факту использования, которые прежде были экономически нецелесообразны без блокчейна. Представьте, что Netflix платит за каждый просмотр, а Spotify — за каждую песню вместо подписки. Вот лишь несколько возможных идей, однако весь спектр возможностей ограничен лишь вашим воображением:
AI и агентная коммерция:
- Доступ к AI Agent API: оплата за каждый вызов LLM-инференса, генерации изображений или API AI-модели (см. пример ACK)
- Монетизация MCP-серверов: оплата за инструменты Model Context Protocol, источники данных и специализированные возможности агентов (см. MCPay.tech)
- Платежи между агентами: возможность для автономных агентов совершать транзакции между собой за услуги и данные (см. пример a2a-x402)
- Премиальные данные для обучения AI: продажа доступа к тщательно отобранным датасетам на основе оплаты за запрос
Контент и медиа:
- Платный доступ к статьям: микроплатежи за каждую статью вместо полной подписки
- Стриминг видео/аудио: оплата за просмотр или за минуту контента
- Изображения в высоком разрешении: разблокировка загрузки в полном разрешении после оплаты (см. пример ACK) или пример x402 от Coinbase
- Доступ к премиальным рассылкам: монетизация отдельных выпусков новостной рассылки
Сервисы для разработчиков:
- Тарификация API: оплата за каждый RPC-вызов, запрос к базе данных или вычислительную единицу (см. пример Corbits)
- Бессерверные функции: оплата за каждое выполнение отдельной функции
Данные и аналитика:
- Рыночные данные в реальном времени: ценовые потоки с тарификацией за котировку или тик
- Аналитические дашборды: разблокировка конкретных отчётов или экспортов данных
- Данные IoT-сенсоров: микроплатежи за показания сенсоров из сетей DePIN
Игры и виртуальные товары:
- Доступ к игровым серверам: оплата за сессию или за час
- Загрузка модов и ресурсов: монетизация пользовательского контента
- Взносы за участие в турнирах: автоматическое распределение призового фонда
Разное:
- Фильтрация email/личных сообщений: требование оплаты для доступа к вашему почтовому ящику (защита от спама)
- Вычислительные ресурсы: оплата за час CPU, минуту GPU или гигабайт хранилища
- Доступ к VPN/прокси: тарификация по объёму трафика за ГБ
- Единовременная загрузка файлов: продажа цифровых файлов без издержек на подписку (см. пример ACK)
Ключевое преимущество x402 на Solana — низкие транзакционные издержки (доли цента), что делает настоящие микроплатежи жизнеспособными, а также мгновенный расчёт, обеспечивающий контроль доступа в реальном времени.
SDK и их поддержка Solana
Этот список постоянно обновляется по мере выхода новых SDK или добавления поддержки Solana.
| SDK / Проект | Поддержка Solana | Примечания | Документация / URL |
|---|---|---|---|
| Corbits | Да | Удобный SDK для 402 на Solana | Документация |
| MCPay.tech | Да | Оплата MCP-серверов через микроплатежи | Сайт |
| PayAI Facilitator | Да | Фасилитатор x402 с поддержкой Solana | payai.network |
| Coinbase | Да / Python в разработке | Эталонная реализация протокола x402 от Coinbase | GitHub |
| ACK | В PR | Протокол агентных платежей с поддержкой x402 | GitHub |
| Crossmint | В разработке | Платежи, кошельки, агентные финансы; не специфично для x402 | crossmint.com |
| A2A x402 (Google) | В разработке | Платежи между агентами с использованием Google AI | GitHub |
| Nexus (Thirdweb) | В разработке | Обёртка x402 вокруг API-ключей | Nexus |
| x402scan | Н/П (Обозреватель) | Обозреватель экосистемы x402 (не SDK) | x402scan.com |
| Нативный пример | Да | Минимальный пример без зависимостей | Примеры |
Corbits
SDK с приоритетом Solana для быстрой реализации потоков x402 на Solana. Смотрите документацию: https://corbits.dev/
Пример оплаты RPC-запросов к Solana.
npm install @faremeter/payment-solana @faremeter/fetch @faremeter/info@solana/web3.js
Создайте файл payer-wallet.json и пополните его небольшим количеством USDC и mainnet SOL.
import {Keypair,PublicKey,VersionedTransaction,Connection} from "@solana/web3.js";import { createPaymentHandler } from "@faremeter/payment-solana/exact";import { wrap } from "@faremeter/fetch";import { lookupKnownSPLToken } from "@faremeter/info/solana";import * as fs from "fs";// Load keypair from fileconst keypairData = JSON.parse(fs.readFileSync("./payer-wallet.json", "utf-8"));const keypair = Keypair.fromSecretKey(Uint8Array.from(keypairData));const network = "mainnet-beta";const connection = new Connection("https://api.mainnet.solana.com");const usdcInfo = lookupKnownSPLToken(network, "USDC");const usdcMint = new PublicKey(usdcInfo.address);// Create wallet interfaceconst wallet = {network,publicKey: keypair.publicKey,updateTransaction: async (tx: VersionedTransaction) => {tx.sign([keypair]);return tx;}};// Setup payment handlerconst handler = createPaymentHandler(wallet, usdcMint, connection);const fetchWithPayer = wrap(fetch, { handlers: [handler] });// Call the API - payment happens automaticallyconst response = await fetchWithPayer("https://helius.api.corbits.dev", {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify({jsonrpc: "2.0",id: 1,method: "getBlockHeight"})});const data = await response.json();console.log(data);
npx tsx e2e.ts
Это оплатит RPC-запрос и вернёт высоту блока, используя протокол corbits 402.
Coinbase
Эталонная реализация x402 от Coinbase предоставляет TypeScript-библиотеки и примеры как для клиентских, так и для серверных потоков. Репозиторий включает сквозные тесты, охватывающие 6 различных сценариев SVM (Solana Virtual Machine). Реализация включает верификацию платежей, генерацию квитанций и обработку ошибок.
Ключевые возможности:
- Реализации клиента и сервера на TypeScript
- Утилиты верификации платежей
- Поддержка нескольких схем оплаты (точная сумма, сумма «до»)
- Набор тестов с примерами транзакций Solana
- Разделение логики протокола и бизнес-логики
Простой в использовании пример с минимальным сервером и клиентом можно найти здесь.
const app = express();const PORT = 3000;// Apply x402 payment middleware// This automatically handles:// - 402 responses with payment requirements// - Payment verification (pre-flight checks)// - Transaction submission via facilitator// - Settlement confirmationapp.use(paymentMiddleware(RECIPIENT, {// Protected endpoint: requires $0.001 USDC payment"GET /premium": {price: "$0.0001", // Price in USD (converted to USDC)network: "solana-devnet" // Solana devnet},// Another endpoint with different price"GET /expensive": {price: "$0.001",network: "solana-devnet"}}));// Protected endpoints - only accessible after paymentapp.get("/premium", (req, res) => {res.json({message: "🎉 Premium content accessed!",data: {secret: "This is premium content",timestamp: new Date().toISOString()}});});
Поддержка Python находится в разработке; рабочий сквозной пример доступен здесь.
ACK
Agent Commerce Kit (ACK) поддерживает протокол x402, добавляя к нему критически важные уровни для агентной экономики: верифицируемую идентификацию агентов (ACK-ID) на основе W3C DID/VC и криптографически защищённые квитанции (ACK-Pay) в виде Verifiable Credentials. Это позволяет агентам подтверждать право собственности, проходить аутентификацию автономно и формировать платёжные доказательства, готовые для целей комплаенса, — решая тем самым проблему идентификации и транзакционные барьеры, которые препятствуют участию AI-агентов в коммерции.
Диаграмма потока ACK
Существует PR со сквозным примером, который ещё не влит в основную ветку, но работает. Также доступен живой пример, демонстрирующий платный доступ к изображениям, джукбокс и API для анимации изображений. Исходный код примеров вместе с Twitter-ботом, использующим API для анимации изображений в ленте, можно найти здесь.
MCPay.tech
Микроплатежи за каждый запрос для MCP (Model Context Protocol) серверов по схеме, аналогичной x402. Позволяет разработчикам монетизировать MCP-инструменты и ресурсы, требуя небольшую оплату за каждый вызов API или инструмента, — упрощая тарификацию доступа AI-агентов к премиальным источникам данных, специализированным инструментам или вычислительным ресурсам. Сайт: https://mcpay.tech/
PayAI Facilitator
Фасилитатор x402 с приоритетом Solana, оснащённый живым эхо-мерчантом для тестирования и возврата платежей. На данный момент PayAI берёт на себя все транзакционные комиссии. Сайт: https://payai.network/
A2A x402 (Google)
Инициатива по платежам между агентами, исследующая стандартизированные потоки с требованием оплаты. Поддержка Solana находится в процессе разработки; рабочий пример чата можно найти здесь
Crossmint
Crossmint — универсальная платформа для компаний и агентов, обеспечивающая интеграцию крипто-инфраструктуры, включая кошельки, онрампы, оркестрацию стейблкоинов и многое другое. Поддержка x402 на Solana в настоящее время разрабатывается и должна быть завершена к 30.10.2025. Сайт: https://www.crossmint.com/
x402scan
Обозреватель экосистемы x402, предоставляющий подробную статистику, каталог проектов и аналитику по реализациям x402. Отслеживайте объёмы транзакций, находите активных мерчантов и следите за ростом числа платных эндпоинтов в различных сетях. Сайт: https://x402scan.com/
Nexus (Thirdweb)
Thirdweb Nexus разрабатывает обёртку x402 вокруг API-ключей (в настоящее время в разработке). Сайт: https://nexus.thirdweb.com/
Нативный пример
Нативный пример без зависимостей с минимальным сервером и клиентом.
Вы можете клонировать репозиторий и запустить пример:
git clone https://github.com/Woody4618/x402-solana-examplesnpm install# Terminal 1: Start servernpm run usdc:server# Terminal 2: Run client (requires devnet USDC)npm run usdc:client
Обзор потока
- Клиент отправляет запрос к
/premium. - Сервер отвечает 402 с условиями оплаты: получатель, сумма.
- Клиент формирует транзакцию с инструкцией перевода получателю.
- Клиент повторяет запрос к
/premiumс payload транзакции. - Сервер верифицирует транзакцию и отправляет её в сеть.
- После подтверждения сервер отвечает 200.
Альтернатива для Solana: В Solana можно реализовать вариант, при котором клиент отправляет транзакцию напрямую в сеть с инструкцией memo (вместо того чтобы отправлять её на сервер), а затем передаёт серверу только подпись транзакции для верификации. Это решает проблему разрыва соединения — если клиент отключается после оплаты, но до получения контента, он может повторить запрос с той же подписью, поскольку платёж уже подтверждён в блокчейне. Однако такой подход отклоняется от стандартного потока x402.org (который предполагает, что сервер транслирует транзакцию), поэтому в данном примере мы используем стандартный подход.
Примечание: код этого примера не прошёл аудит, не готов к использованию в производственной среде и предназначен исключительно в демонстрационных целях. Он показывает, что x402 можно реализовать без зависимостей и без использования фасилитатора. Использование фасилитатора удобно, поскольку он скрывает сложность и может брать на себя комиссии за транзакции, однако он также может стать единой точкой отказа — например, когда на кошельке фасилитатора заканчиваются средства. Пример сервера отправляет транзакции, подписанные клиентом. Возможно, вам потребуется их проверить с помощью validator.
Минимальный сервер (Express)
// x402-compliant server with USDC (SPL Token) paymentsimport express from "express";import { Connection, PublicKey, Transaction } from "@solana/web3.js";import { TOKEN_PROGRAM_ID, getAssociatedTokenAddress } from "@solana/spl-token";const connection = new Connection("https://api.devnet.solana.com", "confirmed");// Devnet USDC mint addressconst USDC_MINT = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");// Your recipient wallet address (same as SOL example)const RECIPIENT_WALLET = new PublicKey("seFkxFkXEY9JGEpCyPfCWTuPZG9WK6ucf95zvKCfsRX");// Derive the recipient's USDC token account (Associated Token Account)const RECIPIENT_TOKEN_ACCOUNT = await getAssociatedTokenAddress(USDC_MINT,RECIPIENT_WALLET);// Picking a small USDC priceconst PRICE_USDC = 100; // 0.0001 USDCconst app = express();app.use(express.json());// x402 endpoint - Quote or verify paymentapp.get("/premium", async (req, res) => {const xPaymentHeader = req.header("X-Payment");// If client provided X-Payment header, verify and submit transactionif (xPaymentHeader) {try {// Decode base64 and parse JSON (x402 standard)const paymentData = JSON.parse(Buffer.from(xPaymentHeader, "base64").toString("utf-8")) as {x402Version: number;scheme: string;network: string;payload: {serializedTransaction: string;};};console.log("Received USDC payment proof from client");console.log(` Network: ${paymentData.network}`);// Deserialize the transactionconst txBuffer = Buffer.from(paymentData.payload.serializedTransaction,"base64");const tx = Transaction.from(txBuffer);console.log("Verifying SPL Token transfer instructions...");// Step 1: Introspect and decode SPL Token transfer instructionconst instructions = tx.instructions;let validTransfer = false;let transferAmount = 0;for (const ix of instructions) {// Check if this is a Token Program instructionif (ix.programId.equals(TOKEN_PROGRAM_ID)) {// SPL Token Transfer instruction layout:// [0] = instruction type (3 for Transfer)// [1-8] = amount (u64, little-endian)if (ix.data.length >= 9 && ix.data[0] === 3) {// Read the amount (u64 in little-endian, starts at byte 1)transferAmount = Number(ix.data.readBigUInt64LE(1));// Verify accounts: [source, destination, owner]if (ix.keys.length >= 2) {const destAccount = ix.keys[1].pubkey;if (destAccount.equals(RECIPIENT_TOKEN_ACCOUNT) &&transferAmount >= PRICE_USDC) {validTransfer = true;console.log(` ✓ Valid USDC transfer: ${transferAmount / 1000000} USDC`);console.log(` To: ${RECIPIENT_TOKEN_ACCOUNT.toBase58()}`);break;}}}}}if (!validTransfer) {return res.status(402).json({error:"Transaction does not contain valid USDC transfer to recipient with correct amount",details:transferAmount > 0? `Found transfer of ${transferAmount}, expected ${PRICE_USDC}`: "No valid token transfer instruction found"});}// Step 2: Simulate the transaction BEFORE submittingconsole.log("Simulating transaction...");try {const simulation = await connection.simulateTransaction(tx);if (simulation.value.err) {console.error("Simulation failed:", simulation.value.err);return res.status(402).json({error: "Transaction simulation failed",details: simulation.value.err,logs: simulation.value.logs});}console.log(" ✓ Simulation successful");} catch (simError) {console.error("Simulation error:", simError);return res.status(402).json({error: "Failed to simulate transaction",details:simError instanceof Error ? simError.message : "Unknown error"});}// Step 3: Submit the transaction (only if verified and simulated successfully)// Note: Solana blockchain automatically rejects duplicate transaction signaturesconsole.log("Submitting transaction to network...");const signature = await connection.sendRawTransaction(txBuffer, {skipPreflight: false,preflightCommitment: "confirmed"});console.log(`Transaction submitted: ${signature}`);// Wait for confirmationconst confirmation = await connection.confirmTransaction(signature,"confirmed");if (confirmation.value.err) {return res.status(402).json({error: "Transaction failed onchain",details: confirmation.value.err});}// Fetch the transaction to verify payment detailsconst confirmedTx = await connection.getTransaction(signature, {commitment: "confirmed",maxSupportedTransactionVersion: 0});if (!confirmedTx) {return res.status(402).json({error: "Could not fetch confirmed transaction"});}// Verify token balance changes from transaction metadataconst postTokenBalances = confirmedTx.meta?.postTokenBalances ?? [];const preTokenBalances = confirmedTx.meta?.preTokenBalances ?? [];// Find the recipient's token account in the balance changeslet amountReceived = 0;for (let i = 0; i < postTokenBalances.length; i++) {const postBal = postTokenBalances[i];const preBal = preTokenBalances.find((pre) => pre.accountIndex === postBal.accountIndex);// Check if this is the recipient's accountconst accountKey =confirmedTx.transaction.message.staticAccountKeys[postBal.accountIndex];if (accountKey && accountKey.equals(RECIPIENT_TOKEN_ACCOUNT)) {const postAmount = postBal.uiTokenAmount.amount;const preAmount = preBal?.uiTokenAmount.amount ?? "0";amountReceived = Number(postAmount) - Number(preAmount);break;}}if (amountReceived < PRICE_USDC) {return res.status(402).json({error: `Insufficient payment: received ${amountReceived}, expected ${PRICE_USDC}`});}console.log(`Payment verified: ${amountReceived / 1000000} USDC received`);console.log(`View transaction: https://explorer.solana.com/tx/${signature}?cluster=devnet`);// Payment verified! Return premium contentreturn res.json({data: "Premium content - USDC payment verified!",paymentDetails: {signature,amount: amountReceived,amountUSDC: amountReceived / 1000000,recipient: RECIPIENT_TOKEN_ACCOUNT.toBase58(),explorerUrl: `https://explorer.solana.com/tx/${signature}?cluster=devnet`}});} catch (e) {console.error("Payment verification error:", e);return res.status(402).json({error: "Payment verification failed",details: e instanceof Error ? e.message : "Unknown error"});}}// No payment provided - return 402 with payment detailsconsole.log("New USDC payment quote requested");return res.status(402).json({payment: {recipientWallet: RECIPIENT_WALLET.toBase58(),tokenAccount: RECIPIENT_TOKEN_ACCOUNT.toBase58(),mint: USDC_MINT.toBase58(),amount: PRICE_USDC,amountUSDC: PRICE_USDC / 1000000,cluster: "devnet",message: "Send USDC to the token account"}});});app.listen(3001, () => console.log("x402 USDC server listening on :3001"));
Минимальный клиент (Node)
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";import {createTransferInstruction,getOrCreateAssociatedTokenAccount,createAssociatedTokenAccountInstruction,getAccount} from "@solana/spl-token";import fetch from "node-fetch";import { readFileSync } from "fs";const connection = new Connection("https://api.devnet.solana.com", "confirmed");const keypairData = JSON.parse(readFileSync("./pay-in-usdc/client.json", "utf-8"));const payer = Keypair.fromSecretKey(Uint8Array.from(keypairData));async function run() {// 1) Request payment quote from serverconst quote = await fetch("http://localhost:3001/premium");const q = (await quote.json()) as {payment: {tokenAccount: string;mint: string;amount: number;amountUSDC: number;cluster: string;};};if (quote.status !== 402) throw new Error("Expected 402 quote");const recipientTokenAccount = new PublicKey(q.payment.tokenAccount);const mint = new PublicKey(q.payment.mint);const amount = q.payment.amount;console.log("USDC Payment required:");console.log(` Recipient Token Account: ${q.payment.tokenAccount}`);console.log(` Mint (USDC): ${q.payment.mint}`);console.log(` Amount: ${q.payment.amountUSDC} USDC (${amount} smallest units)`);// 2) Get or create the payer's associated token accountconsole.log("\nChecking/creating associated token account...");const payerTokenAccount = await getOrCreateAssociatedTokenAccount(connection,payer,mint,payer.publicKey);console.log(` Payer Token Account: ${payerTokenAccount.address.toBase58()}`);// Check if payer has enough USDCconst balance = await connection.getTokenAccountBalance(payerTokenAccount.address);console.log(` Current Balance: ${balance.value.uiAmountString} USDC`);if (Number(balance.value.amount) < amount) {throw new Error(`Insufficient USDC balance. Have: ${balance.value.uiAmountString}, Need: ${q.payment.amountUSDC}`);}// 3) Check if recipient token account exists, create if notconsole.log("\nChecking recipient token account...");let recipientAccountExists = false;try {await getAccount(connection, recipientTokenAccount);recipientAccountExists = true;console.log(" ✓ Recipient token account exists");} catch (error) {console.log(" ⚠ Recipient token account doesn't exist, will create it");}// 4) Create USDC transfer transaction (but DON'T submit it)const { blockhash } = await connection.getLatestBlockhash();const tx = new Transaction({feePayer: payer.publicKey,blockhash,lastValidBlockHeight: (await connection.getLatestBlockhash()).lastValidBlockHeight});// Add create account instruction if neededif (!recipientAccountExists) {// We need to know the recipient wallet address to create the ATA// The server should provide this, so let's get it from the wallet address// Usually the server will already have the token account, but to be sure for the examples// lets create one.const recipientWallet = new PublicKey("seFkxFkXEY9JGEpCyPfCWTuPZG9WK6ucf95zvKCfsRX");const createAccountIx = createAssociatedTokenAccountInstruction(payer.publicKey, // payerrecipientTokenAccount, // associated token account addressrecipientWallet, // ownermint // mint);tx.add(createAccountIx);console.log(" + Added create token account instruction");}// Add transfer instructionconst transferIx = createTransferInstruction(payerTokenAccount.address, // sourcerecipientTokenAccount, // destinationpayer.publicKey, // owneramount // amount in smallest units);tx.add(transferIx);// Sign the transaction (but don't send it, the server will do that)tx.sign(payer);// Serialize the signed transactionconst serializedTx = tx.serialize().toString("base64");console.log("\nTransaction created and signed (not submitted yet)");console.log(` Instructions: ${tx.instructions.length}`);// 4) Send X-Payment header with serialized transaction (x402 standard)const paymentProof = {x402Version: 1,scheme: "exact",network:q.payment.cluster === "devnet" ? "solana-devnet" : "solana-mainnet",payload: {serializedTransaction: serializedTx}};// Base64 encode the payment proofconst xPaymentHeader = Buffer.from(JSON.stringify(paymentProof)).toString("base64");console.log("\nSending payment proof to server (server will submit transaction)...");const paid = await fetch("http://localhost:3001/premium", {headers: {"X-Payment": xPaymentHeader}});const result = (await paid.json()) as {data?: string;error?: string;paymentDetails?: {signature: string;amount: number;amountUSDC: number;recipient: string;explorerUrl: string;};};console.log("\nServer response:");console.log(result);// Display explorer link if payment was successfulif (result.paymentDetails?.explorerUrl) {console.log("\n🔗 View transaction on Solana Explorer:");console.log(result.paymentDetails.explorerUrl);}}run().catch(console.error);
Улучшения
- Рассмотрите возможность возврата JWT после оплаты, чтобы клиенты могли на короткое время повторно использовать доступ. ACK значительно упрощает это.
- Убедитесь, что ваши ключи не утекают, и поместите их в переменные окружения.
Is this page helpful?