Интеграция игры с блокчейн-кошельком

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Интеграция игры с блокчейн-кошельком
Средний
~3-5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1121
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    858

Интеграция игры с блокчейн-кошельком

Интеграция кошелька — это UX-проблема не меньше, чем техническая. Пользователи приходят играть, а не разбираться с gas, approve транзакциями и seed phrases. Задача разработчика — сделать так, чтобы блокчейн был невидим для игрока, пока он ему не нужен. Разберём конкретные паттерны для разных типов игр.

Выбор метода подключения

Внешние кошельки (MetaMask, Phantom)

Стандарт для аудитории, которая уже в крипте. Через Wagmi (React) или WalletConnect AppKit подключение выглядит стандартно:

import { useConnect, useAccount, useSignMessage } from "wagmi";
import { injected, walletConnect } from "wagmi/connectors";

function WalletConnect() {
  const { connect, connectors } = useConnect();
  const { address, isConnected } = useAccount();

  if (isConnected) return <GameLobby address={address} />;

  return (
    <div>
      <button onClick={() => connect({ connector: injected() })}>
        MetaMask
      </button>
      <button onClick={() => connect({ connector: walletConnect({ projectId: WC_PROJECT_ID }) })}>
        WalletConnect
      </button>
    </div>
  );
}

Embedded wallets (Privy, Web3Auth) для массовой аудитории

Для casual игр — пользователи не хотят устанавливать расширения. Embedded wallet создаётся автоматически при регистрации через Google/Apple:

import { usePrivy, useWallets } from "@privy-io/react-auth";

function GameAuth() {
  const { login, authenticated, user } = usePrivy();
  const { wallets } = useWallets();

  const embeddedWallet = wallets.find(w => w.walletClientType === "privy");

  // Для игрока это просто "войти через Google"
  // Кошелёк создаётся в фоне автоматически
  if (!authenticated) {
    return <button onClick={login}>Играть</button>;
  }

  return <Game walletAddress={embeddedWallet?.address} />;
}

Session keys: взаимодействие без подписи каждый раз

Главная UX проблема GameFi — каждое действие требует подписи в MetaMask. Для игры это катастрофа. Session keys решают это: пользователь один раз авторизует сессионный ключ, игра использует его для транзакций автоматически.

import { createKernelAccountClient, createZeroDevPaymasterClient } from "@zerodev/sdk";
import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator";
import { toPermissionValidator, toCallPolicy, ParamCondition } from "@zerodev/permissions";

// Генерируем session key (ephemeral, только для этой игровой сессии)
const sessionPrivateKey = generatePrivateKey();
const sessionAccount = privateKeyToAccount(sessionPrivateKey);

// Политика: session key может только вызывать GAME_CONTRACT функции
// с ограниченными параметрами
const callPolicy = toCallPolicy({
  permissions: [{
    target: GAME_CONTRACT_ADDRESS,
    functionName: "claimReward",
    // Необязательно: ограничения на параметры
  }, {
    target: GAME_CONTRACT_ADDRESS,
    functionName: "useItem",
  }]
});

const permissionValidator = await toPermissionValidator(publicClient, {
  signer: sessionAccount,
  policies: [callPolicy],
  validUntil: Math.floor(Date.now() / 1000) + 3600, // 1 час
});

// Пользователь подписывает один раз — активацию session key
const kernelClient = await createKernelAccountClient({
  account: kernelAccount,
  // ...bundler config
});

После этого игра может отправлять транзакции от имени пользователя без дополнительных подтверждений, но только в рамках разрешённых операций.

Unity интеграция

WebGL: JSLib bridge

В Unity WebGL нет прямого доступа к MetaMask — нужен JavaScript bridge:

// Plugins/WebGL/wallet.jslib
mergeInto(LibraryManager.library, {
  ConnectWallet: async function() {
    if (typeof window.ethereum === 'undefined') {
      alert('MetaMask not installed');
      return;
    }
    const accounts = await window.ethereum.request({
      method: 'eth_requestAccounts'
    });
    // Отправляем адрес обратно в Unity
    unityInstance.SendMessage('WalletManager', 'OnWalletConnected', accounts[0]);
  },

  SignMessage: async function(messagePtr) {
    const message = UTF8ToString(messagePtr);
    const accounts = await window.ethereum.request({ method: 'eth_accounts' });
    const signature = await window.ethereum.request({
      method: 'personal_sign',
      params: [message, accounts[0]],
    });
    unityInstance.SendMessage('WalletManager', 'OnMessageSigned', signature);
  }
});
// C# side в Unity
using System.Runtime.InteropServices;

public class WalletManager : MonoBehaviour {
    [DllImport("__Internal")]
    private static extern void ConnectWallet();

    [DllImport("__Internal")]
    private static extern void SignMessage(string message);

    public string ConnectedAddress { get; private set; }

    public void Connect() {
        #if UNITY_WEBGL && !UNITY_EDITOR
        ConnectWallet();
        #endif
    }

    // Callback из JavaScript
    public void OnWalletConnected(string address) {
        ConnectedAddress = address;
        GameEvents.WalletConnected?.Invoke(address);
    }
}

Mobile: WalletConnect deep links

На мобильном Unity использует deep links для открытия мобильного кошелька:

using WalletConnectSharp.Unity;

public class MobileWalletConnect : MonoBehaviour {
    private WalletConnect _wc;

    async void Start() {
        _wc = GetComponent<WalletConnect>();
        await _wc.Connect();
    }

    public async Task<string> SendTransaction(string to, string data, string value) {
        var transactionData = new TransactionData {
            from = _wc.Session.Accounts[0],
            to = to,
            data = data,
            value = value
        };
        
        var txHash = await _wc.Session.EthSendTransaction(transactionData);
        return txHash;
    }
}

Обработка транзакций в игровом контексте

Optimistic UI updates

Ждать finality транзакции (10–30 секунд на L2, минуты на mainnet) в игре неприемлемо. Используем optimistic updates: показываем результат немедленно, откатываем если транзакция фейлится.

async function purchaseItem(itemId: number, price: bigint) {
  // 1. Немедленно показываем в UI
  gameState.addItemOptimistic(itemId);

  try {
    // 2. Отправляем транзакцию
    const txHash = await marketplace.write.buyItem([itemId], { value: price });

    // 3. Ждём confirmation в фоне
    const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });

    if (receipt.status === "reverted") {
      // 4. Откатываем если фейл
      gameState.removeItem(itemId);
      showError("Transaction failed");
    }
    // 5. Подтверждаем если успех (UI уже показывает)
  } catch (err) {
    gameState.removeItem(itemId);
    showError("Transaction rejected");
  }
}

Батчинг транзакций

Account Abstraction позволяет отправить несколько операций в одной транзакции:

// Вместо: approve → buy (2 транзакции, 2 подписи)
// Делаем: один multicall через Smart Account
const txHash = await smartAccountClient.sendUserOperation({
  calls: [
    {
      to: TOKEN_ADDRESS,
      abi: erc20Abi,
      functionName: "approve",
      args: [MARKETPLACE_ADDRESS, price],
    },
    {
      to: MARKETPLACE_ADDRESS,
      abi: marketplaceAbi,
      functionName: "buyItem",
      args: [itemId],
    },
  ],
});

Типичные проблемы и решения

Проблема Решение
Пользователь закрыл MetaMask popup Transaction queue + восстановление pending state
Gas estimation failure Статичные gas limits + fallback RPC
Network mismatch Auto switch network через wallet_switchEthereumChain
User rejected transaction Graceful fallback, сохранение прогресса off-chain
Pending transaction stuck Replace with higher gas (speed up) через eth_sendRawTransaction

Ключевой принцип: блокчейн — асинхронный, игра — синхронная. Всегда проектируйте UI так, чтобы pending состояние было явным, а failure — recoverable.