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

@hinkal/react-native

v0.3.4

Published

Prebundled Hinkal SDK for React Native — no Metro config required.

Readme

Hinkal React Native SDK

Hinkal is a privacy middleware and smart-contract SDK for public blockchains that enables confidential transactions and settlement flows without changing wallets, custody, or chains.

@hinkal/react-native is a prebundled build of the Hinkal SDK for React Native and Expo. It includes the polyfills, shims, and worker runtime required on mobile — no custom Metro configuration is needed.

The SDK allows mobile wallets, dApps, and payment apps to integrate protocol-level privacy on Ethereum, Solana, Tron, Polygon, Base, Arbitrum, and Optimism.

With Hinkal SDK, developers can: • Enable private sends between public wallets • Perform confidential payouts and settlements • Route transactions through Hinkal’s privacy contracts without exposing sender, recipient, or amounts • Maintain non-custodial control with optional compliance visibility via viewing keys

Compatibility

| Environment | Supported | Notes | | ------------ | --------- | ------------------ | | React Native | ✅ | v0.74+ | | Expo | ✅ | dev client or bare |

Installation

npm install @hinkal/react-native

Or, yarn:

yarn add @hinkal/react-native

Usage

HinkalProvider

Wrap your app with HinkalProvider before using any SDK function. It runs the React Native bootstrap and mounts the hidden WebView worker host.

import { WagmiProvider } from 'wagmi';
import { HinkalProvider } from '@hinkal/react-native';
import { wagmiConfig } from './wagmiConfig';

export default function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <HinkalProvider>
        <YourApp />
      </HinkalProvider>
    </WagmiProvider>
  );
}

Initialization

After the wallet is connected, initialize a Hinkal instance with your preferred provider helper.

Initializing the SDK creates a Hinkal object that encapsulates:

  • The user's shielded balances
  • Actions the user can perform, such as shielding (depositing), transfers, and swapping
  • Cryptographic keys for privacy-preserving operations

Each provider exposes three prepare helpers:

  • prepare*Hinkal — signs the Hinkal login message and initializes user keys (deterministic signers)
  • prepare*HinkalWithEnclaveSignIn — signs in through the secure enclave and stabilizes identity for non-deterministic signers (smart contract wallets, some hardware wallets)
  • prepare*HinkalFromSignature — initializes user keys from a previously stored signature

wagmi:

import { prepareWagmiHinkal } from '@hinkal/react-native';
// connector: wagmi.Connector
// wagmiConfig: wagmi.Config
const hinkal = await prepareWagmiHinkal(connector, wagmiConfig, hinkalConfig);

ethers.js:

import { prepareEthersHinkal } from '@hinkal/react-native';
const hinkal = await prepareEthersHinkal(signer, hinkalConfig);

Solana:

import { prepareSolanaHinkal } from '@hinkal/react-native';
// connector: SolanaWallet
// ethereumAddress: optional linked EVM address
const hinkal = await prepareSolanaHinkal(connector, ethereumAddress, hinkalConfig);

Tron:

import { prepareTronHinkal } from '@hinkal/react-native';
const hinkal = await prepareTronHinkal(connector, hinkalConfig);

The same WithEnclaveSignIn and FromSignature variants are available for each provider (for example, prepareWagmiHinkalWithEnclaveSignIn, prepareSolanaHinkalFromSignature).

The hinkalConfig is defined as follows:

type HinkalConfig = {
  /** Disables caching in browser localStorage, storing data only in memory. Front-end only. Defaults to false. */
  disableCaching?: boolean;

  /** If true, allows caching in a file locally. Node.js only. Defaults to false. */
  useFileCache?: boolean;

  /**
   * Path to the cache file used for storing temporary data. Node.js only.
   * Defaults to hinkalCache.json in the current working directory.
   */
  cacheFilePath?: string;

  /**
   * Indicator controlling whether the proof should be constructed remotely in secure enclave. Defaults to true.
   */
  generateProofRemotely?: boolean;

  /** Disables automatic merkle tree updates. Defaults to false. */
  disableMerkleTreeUpdates?: boolean;

  /** Override which Tron chain this Hinkal instance targets. */
  tronChainOverride?: number;
};

Identity persistence

When a user connects their wallet, they sign a fixed login message to authenticate with Hinkal. That signature defines their Hinkal identity. Their shielded balances, transaction ability, and all private operations depend on it.

Most wallets return the same signature every time for the same message. Some do not. Smart contract wallets, certain hardware wallets, and other non-deterministic signers may produce a different signature on each login, even for the same address and message.

When that happens, a returning user appears as a new account. Funds deposited in an earlier session remain tied to the original identity and are not accessible from the new one.

Recommended approach: use prepare*HinkalWithEnclaveSignIn instead of prepare*Hinkal. It signs the login message, stores the first signature server-side through the secure enclave, and always initializes with the original identity on later sessions. Solana Ledger wallets are handled automatically.

Manual approach: if you manage identity yourself, call storeAndGetInitialSignature and then either initUserKeysWithSignature or prepare*HinkalFromSignature:

function storeAndGetInitialSignature(
  authSignature: string,
  isSolanaLedger?: boolean,
  txMessageForSolanaLedger?: string,
): Promise<string>;

Parameters:

  • authSignature — signature from the current login session
  • isSolanaLedger — set to true for a Solana Ledger wallet. Defaults to false
  • txMessageForSolanaLedger — base64-encoded transaction message used for Solana Ledger authentication. Required when isSolanaLedger is true

Typical flow with a stored signature:

const initialSignature = await hinkal.storeAndGetInitialSignature(authSignature);
hinkal.initUserKeysWithSignature(initialSignature);

Call this once per session, after wallet connection and before fetching balances or submitting transactions.

You do not need enclave sign-in if your wallet produces deterministic signatures for the same login message on every session — in that case, prepare*Hinkal is sufficient. It is also not needed if you persist the signature yourself via prepare*HinkalFromSignature, or if you use seed-phrase-based login through initUserKeysFromSeedPhrases.

Security

The stored signature is protected at every stage. Before leaving the client, the signature is encrypted with hybrid encryption. The payload is encrypted with a symmetric key, and that key is encrypted with the enclave's public key.

Inside the secure enclave, Google Cloud KMS decrypts the symmetric key. Only then is the signature decrypted. The plaintext signature never leaves the enclave unprotected.

At rest, only the encrypted signature and encrypted key are stored in the database. A caller cannot retrieve a stored signature by wallet address alone. Each request must include any valid signature that proves wallet ownership.

Requests that fail this check are rejected. The first signature stored for a given address is never replaced. Later logins only use a fresh signature to authenticate retrieval of the original.

Shielded balance

Shielded balances are encrypted token holdings stored within the Hinkal protocol. Unlike regular blockchain balances that are publicly visible, shielded balances are hidden from external observers.

After initializing the Hinkal object and calling initUserKeys (or a prepare helper), fetch balances for a specific chain:

function getTotalBalance(
  chainId: number,
  resetCacheBefore?: boolean,
  updateTokensListBefore?: boolean,
): Promise<TokenBalance[]>;

TokenBalance contains chainId, erc20Address, balance, and an optional timestamp.

For reactive UI updates, subscribe to balance changes with USD values:

// Current state keyed by chainId
hinkal.privateBalancesWithUSD;

// Subscribe to updates; returns unsubscribe function
const unsubscribe = hinkal.onPrivateBalancesWithUSDChange((state) => {
  // state: Record<chainId, TokenBalanceWithUsd[]>
});

// Trigger a refresh after a transaction
hinkal.refreshBalance({ chainIdToUpdate: chainId, updateType: PrivateBalanceUpdateType.Fresh });

Shielding: depositing funds to the shielded balance

Shielding moves tokens from a public blockchain address into a private, encrypted balance. Once shielded, tokens are no longer visible on-chain to external observers.

function deposit(
  chainId: number,
  erc20Addresses: string[],
  amountChanges: bigint[],
  preEstimateGas?: boolean,
  returnTxData?: boolean,
): Promise<
  | ethers.TransactionResponse
  | ethers.TransactionRequest
  | string
  | TronWebTypes.Transaction<TronWebTypes.TriggerSmartContract>
>;

where:

  • chainId — target chain
  • erc20Addresses — token contract addresses to deposit
  • amountChanges — corresponding deposit amounts in the token's smallest unit
  • preEstimateGas — if true (default), gas is estimated before executing the deposit
  • returnTxData — if true, returns unsigned transaction data without executing. Defaults to false

On Solana, use depositSolana(chainId, erc20Address, amount).

To shield funds for another user's private address, use depositForOther (EVM/Tron) or depositSolanaForOther (Solana) with their recipientInfo string from getRecipientInfo().

Private Send to Public Address: withdrawing funds from the shielded balance

Private Send to Public Address sends tokens from a shielded balance to any public blockchain address without exposing the sender.

function withdraw(
  chainId: number,
  erc20Addresses: string[],
  deltaAmounts: bigint[],
  recipientAddress: string,
  isRelayerOff: boolean,
  feeToken?: string,
  feeStructureOverride?: FeeStructure,
): Promise<ethers.TransactionResponse | string>;

where:

  • recipientAddress — public address that receives the withdrawn funds
  • isRelayerOff — when false, a relayer handles gas fees; when true, the user pays gas directly
  • feeToken — optional token address used to pay protocol fees
  • feeStructureOverride — optional custom fee structure

Private Send to Private Address: transferring funds from shielded balance

Private Send to Private Address enables fully confidential transfers between shielded balances.

function transfer(
  chainId: number,
  erc20Addresses: string[],
  amountChanges: bigint[],
  recipientAddress: string,
  feeToken?: string,
  feeStructureOverride?: FeeStructure,
): Promise<string>;

where:

  • recipientAddress — recipient's private address string from getRecipientInfo(). Pass it as-is; do not reformat. It is a comma-separated string with five components:
    • stealthAddress — recipient's stealth address (hex, 0x prefix, 64–66 characters)
    • H0[0] — first coordinate of the H0 elliptic-curve point
    • H0[1] — second coordinate of the H0 elliptic-curve point
    • H1[1] — second coordinate of the H1 elliptic-curve point
    • encryptionKey — recipient's encryption public key (hex, 0x prefix, 66 characters)

Private Send from Public to Public addresses

Private Send from Public to Public transfers tokens between two public addresses through Hinkal's privacy infrastructure. Tokens are shielded from the sender, then withdrawn to recipient public addresses on a relayer schedule.

function depositAndWithdraw(
  chainId: number,
  erc20Address: string,
  recipientAmounts: bigint[],
  recipientAddresses: string[],
  txCompletionTime?: number,
  feeStructureOverride?: FeeStructure,
  preEstimateGas?: boolean,
): Promise<DepositAndSendExtendedResult>;

where:

  • erc20Address — token contract address (single-token transfers only)
  • recipientAmounts — amounts to send to each recipient in the token's smallest unit
  • recipientAddresses — public addresses that receive the funds
  • txCompletionTime — optional Unix timestamp in seconds by which all scheduled withdrawals must complete
  • feeStructureOverride — optional custom fee structure
  • preEstimateGas — if true (default), gas is estimated before executing the deposit

The function returns:

type DepositAndSendExtendedResult = {
  depositTxHash: string;
  scheduleId: string;
};

For cross-chain private sends, use depositAndBridge(chainId, erc20Address, recipientBridges, ...) with BridgeRecipient entries that include bridge quotes and destination addresses.

Checking scheduled send status

After depositAndWithdraw or depositAndBridge, fetch scheduled withdrawal status using the returned scheduleId:

function checkSendTransactionStatus(scheduleId: string): Promise<ScheduledTransactionByIdResponse>;

Possible values for ScheduledTransactionStatus:

  • pending — scheduled, waiting for execution time
  • processing — relayer is submitting the withdrawal on-chain
  • waiting_for_relayer — relayer is busy; withdrawal is queued
  • sent_on_chain — submitted on-chain; txHash is available
  • completed — confirmed on-chain
  • failed — withdrawal transaction failed

Swapping tokens from the shielded balance

function swap(
  chainId: number,
  erc20Addresses: string[],
  deltaAmounts: bigint[],
  externalActionId: ExternalActionId,
  swapData: string,
  feeToken?: string,
  feeStructureOverride?: FeeStructure,
): Promise<string>;

Getting swap quotes and calldata:

EVM chains:

function getEvmSwapPrices(
  chainId: number,
  inSwapAmount: string,
  inSwapTokenAddress: string,
  outSwapTokenAddress: string,
): Promise<EvmSwapPrice | null>;
type EvmSwapPrice = {
  outSwapAmountValue: bigint;
  lifiDataValue: string;
};

Returns null if no quote could be fetched. Pass the swap calldata from the quote to swap as swapData, with ExternalActionId.Lifi (outSwapAmountValue is the quoted output amount):

  • LI.FIlifiDataValue with ExternalActionId.Lifi

Solana:

function getSolanaSwapPrices(
  chainId: number,
  inSwapAmount: string,
  inSwapTokenAddress: string,
  outSwapTokenAddress: string,
): Promise<SolanaSwapPrice | null>;
type SolanaSwapPrice = {
  outSwapAmountValue: bigint;
  okxDataValue: string;
};

Returns null if no quote could be fetched. Pass okxDataValue to swap as swapData with ExternalActionId.Okx.

Interacting with smart contracts privately

function actionPrivateWallet(
  chainId: number,
  erc20Addresses: string[],
  deltaAmounts: bigint[],
  onChainCreation: boolean[],
  ops: string[],
  feeToken?: string,
  feeStructureOverride?: FeeStructure,
): Promise<string>;

Generate user operations with emporiumOp:

function emporiumOp(
  contract: ethers.Contract | string,
  func?: string,
  args?: unknown[],
  callDataString?: string,
  invokeWallet?: boolean,
  value?: bigint,
): string;

Stateless interactions (swaps, simple staking) use default invokeWallet: false.

Stateful interactions (reward tracking, voting power) require invokeWallet: true so the call runs from a persistent wallet address.

const operations = [
  hinkal.emporiumOp(usdcContractInstance, 'approve', [swapRouterAddress, amountIn]),
  hinkal.emporiumOp(swapRouterContractInstance, 'exactInputSingle', [swapSingleParams]),
];

Supported Chains

| Chain | Chain ID | Status | | --------------- | ---------- | ------- | | Ethereum | 1 | ✅ Live | | Arbitrum | 42161 | ✅ Live | | Polygon | 137 | ✅ Live | | Base | 8453 | ✅ Live | | Tempo | 4217 | ✅ Live | | BNB | 56 | ✅ Live | | Solana | 501 | ✅ Live | | Tron | 728126428 | ✅ Live | | Arc Testnet | 5042002 | ✅ Live | | Tron Nile | 3448148188 | ✅ Live |

References

Wallet: Hinkal Wallet

Application: Hinkal Pay

Docs: Hinkal Documentation