npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@idosgames/wallet

v0.6.4

Published

Wallet-bridge companion to @idosgames/core: connect browser & mobile wallets (EVM via wagmi/viem/WalletConnect, Solana via wallet-adapter) and move tokens/NFTs in and out of the game through client.blockchain.

Readme

@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.

  • EVM — wagmi + 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

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.
  • @idosgames/wallet/react/solana — the Solana React bindings, kept off /react so an EVM-only game doesn't bundle the Solana adapters.
  • @idosgames/wallet/react/lazy — LazyWalletLogin / LazySolanaWalletLogin / LazyWalletPanel: the sign-in buttons and the in-game deposit/withdraw panel, but each fetches the wallet machinery on demand (LazyWalletLogin on the player's first tap; LazyWalletPanel on mount). This is the only entry with no Reown AppKit in its module graph, which is what makes it safe to render on a login screen or in-game in sandboxed/preview bundlers where AppKit isn't installable. It also re-exports the supported chains as plain objects, so you never have to import the wagmi/chains / viem/chains barrel.

createEvmWalletConfig is memoised per WalletConnect project id: the sign-in button and the wallet panel both call it and get the same wagmi Config object, and a wagmi Config carries the connection in its own store. So a wallet connected on the login screen is already connected in the in-game panel — no reconnect, no second modal. For that to hold, keep both on the same chain set; both default to DEFAULT_EVM_CHAINS, so the simplest correct thing is to pass no chains to either. LazyWalletPanel takes the authenticated client as a prop (like the login button), plus optional appName / onClose / style / walletConnectProjectId.

LazyWalletPanel is ONE panel for both chains: it reads the title's networks and loads the EVM panel (WalletPanel) or the Solana one (SolanaWalletPanel, /react/solana). Both render the same form (walletPanelView.tsx: connect, network, token, in-game balance, amount, Deposit / Withdraw) — a chain only supplies how it connects and moves tokens. On Solana that is Reown AppKit for the wallet and createPlatformPoolAdapter (deposit_spl / withdraw_spl against the platform pool, network.RewardPoolAddress from the engine only); transactions go to the title's cluster (ChainID 103 → devnet, else mainnet). createSolanaWalletConfig is memoised per project id too, so a Solana wallet connected at sign-in stays connected in the panel.

Inside the idosgames.com frame a game's wallet button calls openPlatformWalletPanel() (also on /react/lazy, with isEmbeddedInPlatform / isEmbeddedOnForeignSite): the site shows its own wallet card — crypto in a frame goes through the site only.

Every subpath declares a browser export condition pointing at the ESM build. That is load-bearing, not cosmetic: some browser bundlers (CodeSandbox's classic Sandpack bundler among them) rank the require condition above import and would otherwise take the CommonJS build — where a dynamic import() cannot survive, so the lazy entry would eagerly require AppKit.

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 / submitEvmNftWithdrawal — never 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).

Embedded in an idosgames.com iframe

A game embedded in an idosgames.com iframe cannot reach the player's wallet on its own — deposit/withdraw goes through the buttons in the site's wallet card next to the game. The @idosgames/wallet/react/lazy components handle this by themselves:

  • LazyWalletLogin / LazySolanaWalletLogin stay a normal "Sign in with wallet" button. Inside the platform's frame the tap goes to loginWithWalletViaPlatform: the SITE's already-connected wallet signs the engine's login message (the site connects one first if needed), so the wallet prompt comes from idosgames.com, and the game exchanges the signature for a session as usual. The site signs nothing but the engine's login template for its own page's title. (2026-09-23 — before that the button was replaced by a notice and wallet sign-in inside the frame was simply gone.)
  • LazyWalletPanel renders nothing in a frame — no notice either.
  • Embedded on somebody else's site through idosgames.com/embed/{id} (two frames deep, isEmbeddedOnForeignSite()) the sign-in button renders nothing too: that page has no wallet of ours to sign with.

Every bridge and login flow that talks to the wallet directly (depositTokenEvm, withdrawTokenEvm, loginWithWalletEvm, their Solana equivalents, payWithWalletEvm) refuses with error: WALLET_USE_SITE_PANEL if called inside a frame anyway, so a game that rolls its own UI can check isEmbeddedInPlatform() up front — and call loginWithWalletViaPlatform({ client, networkID, family: "evm" | "solana" }) for sign-in:

import {
  isEmbeddedInPlatform,
  onWalletBalanceChanged,
} from "@idosgames/wallet";

if (isEmbeddedInPlatform()) {
  // hide your own wallet UI — the player uses the site's panel instead
}

// Refresh the on-screen balance once the player finishes an operation there:
const unsubscribe = onWalletBalanceChanged(() => {
  client.blockchain.getUserState();
});

A game opened by its own direct link (not embedded) is unaffected — every flow works exactly as before there.

Linking a wallet (play access by token balance)

When a title's login mode requires a token balance (cfg.Blockchain.PlayAccess, AnyWithLinkedWallet), a player who signed in by e-mail or through the site attaches a wallet to the account by signature — not a second login. Every sign-in button has a link mode:

<LazyWalletLogin
  mode="link"
  client={client}
  networkID="bsc"
  onLinked={(pass) => (pass?.Granted ? enterGame() : showWhyNot(pass))}
/>

Framework-agnostic: linkWalletEvm, linkWalletSolana, and linkWalletViaPlatform (inside the idosgames.com frame — the site's wallet signs). The server checks the balance against exactly the linked wallet and returns the pass as data.PlayAccess. The host template wires this into its play-access screen already (renderWalletLink in src/walletLogin.tsx).

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.