npm.io
0.1.2 • Published yesterday

@idosgames/wallet

Licence
MIT
Version
0.1.2
Deps
3
Size
121 kB
Vulns
0
Weekly
0

@idosgames/wallet

The on-chain half of the iDosGames blockchain flow. @idosgames/core's client.blockchain is deliberately report-only: it verifies deposits and issues signed withdrawals but never signs or broadcasts a transaction. This package is the missing piece — it connects browser & mobile wallets and runs the exact RewardPool contract calls, threading them through the request → submit → confirm / approve → deposit → report lifecycle so a player can move tokens and NFTs in and out of the game.

  • EVMwagmi + viem + WalletConnect. Browser (MetaMask / any injected wallet) and mobile (via WalletConnect) are both first-class.
  • Solana@solana/wallet-adapter for connection; the on-chain instruction building is delegated to a small SolanaProgramAdapter you implement with your program's IDL (see Solana).

Everything stays server-authoritative: the bridge only submits what the backend signed/verified and mirrors the confirmed result into the core cache.

Install

npm i @idosgames/wallet @idosgames/core
# EVM peer deps:
npm i wagmi viem @tanstack/react-query
# Solana peer deps (only if you support Solana networks):
npm i @solana/web3.js @solana/wallet-adapter-react @solana/wallet-adapter-base @solana/wallet-adapter-wallets

Two entry points:

  • @idosgames/wallet — framework-agnostic bridge functions (viem + @solana/web3.js only). Use these directly if you're not on React.
  • @idosgames/wallet/react — wagmi/React hooks + providers. Everything below uses these.

Operation category

The updated RewardPool contract tags each operation with a string category (default "game_topup"; "community_reward" is the other known value). Deposits read it back from the on-chain tx; withdrawals sign it into the hash, so it must be submitted on-chain verbatim — the bridge handles that. Pass a category to any deposit/withdraw call, or omit it for "game_topup". Constants live in @idosgames/core as BlockchainOperationCategory.

EVM

1. Set up the provider

createEvmWalletConfig builds a wagmi config wired for both browser and mobile wallets. Wrap your app with IDosGamesWalletProvider (WagmiProvider + react-query) once.

import { polygon } from "viem/chains";
import {
  createEvmWalletConfig,
  IDosGamesWalletProvider,
} from "@idosgames/wallet/react";

const wagmiConfig = createEvmWalletConfig({
  chains: [polygon], // match the EVM networks in your title's blockchain config
  walletConnectProjectId: "<your walletconnect cloud id>", // enables MOBILE wallets
  appName: "My Game",
});

export function Root() {
  return (
    <IDosGamesWalletProvider wagmiConfig={wagmiConfig}>
      <App />
    </IDosGamesWalletProvider>
  );
}

Connect/disconnect with wagmi's own hooks (useConnect, useAccount, useDisconnect) — the injected() connector covers MetaMask & browser extensions, walletConnect() opens the QR/deep-link modal for phones.

2. Deposit a token

useEvmBridge(client, titleID) binds the connected wallet to the four flows. Amounts are raw on-chain units — scale with viem's parseUnits.

import { parseUnits } from "viem";
import { useEvmBridge } from "@idosgames/wallet/react";
import type { BlockchainNetworkDefinition } from "@idosgames/core";

function DepositButton({ client, network, usdtAddress }) {
  const bridge = useEvmBridge(client, "my-title-id");

  async function deposit() {
    // approve → depositERC20(token, amount, userID, titleID, category) → report to backend
    const res = await bridge.depositToken({
      network, // BlockchainNetworkDefinition from getDefinitions()
      tokenAddress: usdtAddress, // ERC-20 contract
      amount: parseUnits("25", 6), // 25 USDT (6 decimals) as raw units
      // category defaults to "game_topup"
    });
    if (!res.ok) return alert(`${res.stage}: ${res.error}`);
    // core cache balance is already updated; res.data is DepositTokenResponse
  }

  return (
    <button disabled={!bridge.connected} onClick={deposit}>
      Deposit 25 USDT
    </button>
  );
}
3. Withdraw a token
const res = await bridge.withdrawToken({
  currencyID: "usdt",
  networkID: "polygon",
  walletAddress: bridge.account!, // destination — usually the connected wallet
  amount: "25.00", // human decimal; server scales & signs raw units
});
if (!res.ok) {
  // If it failed AFTER the debit, res.titleTransactionID is set — recover with
  // retryWithdrawal (while Pending) or confirmWithdrawal, NEVER a fresh request.
  console.error(res.stage, res.error, res.titleTransactionID);
}

The bridge runs requestTokenWithdrawal (debits in-game) → withdrawERC20 on-chain → confirmWithdrawal. A BridgeFailure tells you exactly where it stopped via stage (request / withdraw-onchain / confirm) so you can recover correctly — see Failure & recovery.

4. NFTs
// Deposit: safeTransferFrom(account, pool, id, amount, abi.encode(userID,titleID,category))
await bridge.depositNft({
  network,
  nftContractAddress,
  tokenId: 42n,
  amount: 1n,
});

// Withdraw: requestNFTWithdrawal → withdrawERC1155 → confirmWithdrawal
await bridge.withdrawNft({
  itemID,
  networkID: "polygon",
  walletAddress: bridge.account!,
  amount: "1",
});

Solana

The Solana RewardPool is a custom program whose instruction/account layout isn't in this SDK — so you provide a SolanaProgramAdapter (two methods: depositSpl and submitWithdrawal) built with your program's IDL / @solana/web3.js and the connected wallet from @solana/wallet-adapter-react. The SDK-side orchestration is identical to EVM.

import {
  SolanaWalletBridgeProvider,
  useSolanaBridge,
} from "@idosgames/wallet/react";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets";
import type { SolanaProgramAdapter } from "@idosgames/wallet";

// Wrap (alongside IDosGamesWalletProvider if you also support EVM):
<SolanaWalletBridgeProvider
  endpoint="https://api.mainnet-beta.solana.com"
  wallets={[new PhantomWalletAdapter()]}
>
  <App />
</SolanaWalletBridgeProvider>;

// Your program integration:
const adapter: SolanaProgramAdapter = {
  async depositSpl({ mint, amountRaw, userID, titleID, category }) {
    /* build + send the DepositSpl tx with your IDL; return the signature */
  },
  async submitWithdrawal(sig) {
    /* build + send withdraw_spl with the ed25519 sig-verify ix; return the signature */
  },
};

function Screen({ client }) {
  const bridge = useSolanaBridge(client, "my-title-id", adapter);
  // bridge.depositToken({ network, mint, amountRaw }) / bridge.withdrawToken({ currencyID, networkID, amount })
}

Failure & recovery

Every bridge call resolves to a BridgeResult<T>:

type BridgeResult<T> =
  | { ok: true; onChainTxHash: string; data: T }
  | {
      ok: false;
      stage: BridgeStage; // where it stopped
      error: string;
      onChainTxHash?: string; // set if the asset-moving tx already landed
      titleTransactionID?: string; // set if a withdrawal already debited in-game
    };

Recovery rules (the bridge never double-charges, but you drive the retry):

  • stage: "approve" | "deposit-onchain" — nothing was reported; safe to retry the whole deposit.
  • stage: "report" — the on-chain tx (onChainTxHash) landed but the backend didn't credit it; retry client.blockchain.depositToken/depositNFT with that hash.
  • stage: "withdraw-onchain" with a titleTransactionID — the withdrawal was already debited in-game but not submitted on-chain. Get a fresh signature with client.blockchain.retryWithdrawal(titleTransactionID) (while Pending) and submit it with submitEvmTokenWithdrawal / submitEvmNftWithdrawalnever call withdrawToken again (that debits twice).
  • stage: "confirm" with onChainTxHash + titleTransactionID — the tx landed but the backend confirm didn't stick; retry client.blockchain.confirmWithdrawal(titleTransactionID, onChainTxHash).

Framework-agnostic core

Not on React? Import the same flows from @idosgames/wallet and pass viem clients yourself:

import { depositTokenEvm, withdrawTokenEvm } from "@idosgames/wallet";

const clients = { publicClient, walletClient, account }; // your viem clients
await depositTokenEvm({
  client,
  clients,
  network,
  tokenAddress,
  amount,
  titleID,
});

Notes

  • The RewardPool ABI here mirrors the backend's RewardPoolEvmV2 signing (field order/names of withdrawERC20/withdrawERC1155/depositERC20 must match, or signatures fail on-chain).
  • After a deposit/withdrawal the core balance cache is fresh, but client.data.user.state.Blockchain (pending list, stats) is not — call client.blockchain.getUserState() to refresh it. See the blockchain-system skill for the full server-side surface, withdrawal gates, and gotchas.

Keywords