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

@oasisprotocol/privana-sdk

v0.5.4

Published

React SDK for Privana - manage deposits, withdrawals, locks, and transfers

Readme

@oasisprotocol/privana-sdk

React SDK for Privana - manage deposits, withdrawals, locks, and transfers with ease.

Installation

npm install @oasisprotocol/privana-sdk
# or
bun add @oasisprotocol/privana-sdk

Peer Dependencies

This SDK requires the following peer dependencies:

npm install react react-dom wagmi viem @tanstack/react-query

Quick Start

1. Wrap your app with the PrivanaProvider

import { PrivanaProvider } from '@oasisprotocol/privana-sdk'
import { WagmiProvider } from 'wagmi'
import { QueryClientProvider } from '@tanstack/react-query'

function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        <PrivanaProvider
          networkConfig={{
            name: 'Sapphire Testnet',
            chainId: 23295,
            apiUrl: 'https://api.testnet.privana.finance',
            accountingContract: '0xYourContractAddress',
          }}
          hostedAuth={{
            clientId: 'honoroll-web',
            redirectUri: 'https://honoroll.example.com/auth/callback',
          }}
        >
          <YourApp />
        </PrivanaProvider>
      </QueryClientProvider>
    </WagmiProvider>
  )
}

accountingContract must be the deployed Privana accounting contract address for the same environment as apiUrl.

2. Or build custom UI with hooks

import {
  useBalance,
  useDeposit,
  useTotalLockedBalance,
  useWithdraw,
} from '@oasisprotocol/privana-sdk'

function CustomWallet() {
  const { balanceFormatted } = useBalance()
  const { totalLocked } = useTotalLockedBalance()
  const { deposit } = useDeposit()
  const { withdraw } = useWithdraw()

  return (
    <div>
      <p>Available: {balanceFormatted}</p>
      <p>Total locked: {totalLocked}</p>
      <button onClick={() => deposit({ amount: 1000000n, tokenId: '0xYourTokenId' })}>
        Deposit
      </button>
    </div>
  )
}

Private Reads

useBalance, useBatchBalances, useHistory, useLockedFunds, useExpiredLocks, and useTotalLockedBalance support two auth modes:

useHistory({ offset: -1, limit: 50 }) fetches one authenticated history page. Non-negative offsets count pages from the oldest entries, negative offsets count from the end, and each page is returned oldest-to-newest. limit must be between 0 and 100. History entries include lock-lifecycle kinds (modifyLock, unlockLock) and directional transfer kinds (transferFromLockOut/transferFromLockIn, transferBalanceOut/transferBalanceIn). On the outbound (Out) kinds counterparty is the recipient, and on the inbound (In) kinds it is the sender.

Direct SIWE private reads

Default mode for same-origin Privana browser integrations:

  • GET /v1/accounting/auth/domain
  • GET /v1/accounting/auth/nonce?address=0x...
  • POST /v1/accounting/auth/login

The hooks cache the returned X-SIWE-Token, dedupe concurrent auth so a group of mounted private-read hooks only triggers one sign prompt, and retry once on 401 by re-authenticating through the same shared in-flight auth request.

Direct in-app SIWE auth (siweAuth)

For same-origin apps that want an explicit authenticated session (and JWT-authenticated writes), enable siweAuth on PrivanaProvider. The connected wallet signs an EIP-4361 message in-app and useSiweAuth() exposes login, logout, session, and the raw tokens:

import { PrivanaProvider, useSiweAuth } from '@oasisprotocol/privana-sdk'
;<PrivanaProvider siweAuth={{ autoLogin: true }}>
  <AuthGate />
</PrivanaProvider>

siweAuth accepts true, or an object with autoLogin (default true) and persistJwt (default false). It is mutually exclusive with hostedAuth.

Persistent JWT sessions (persistJwt)

Set persistJwt: true to mirror the session in localStorage (key scoped by API URL and chain ID) so a page reload restores an active session without another signature prompt:

;<PrivanaProvider siweAuth={{ autoLogin: true, persistJwt: true }}>
  <App />
</PrivanaProvider>

Logout captures the refresh token, clears local and cross-tab state immediately, suppresses automatic re-login, and attempts server-side revocation.

Security note: storing long-lived refresh credentials in localStorage makes them reachable from any JavaScript running on the page. An XSS vulnerability could exfiltrate them and forge a session, so only enable persistJwt on origins you fully control.

Hosted redirect auth for cross-domain apps

For widget or cross-domain frontends, configure hostedAuth on PrivanaProvider and use useHostedRedirectAuth() to start the hosted sign-in and complete it on your callback route:

import { PrivanaProvider, useBalance, useHostedRedirectAuth } from '@oasisprotocol/privana-sdk'

function HostedAuthButton() {
  const { login, logout, refresh, isAuthenticated, isLoading, error, session } =
    useHostedRedirectAuth()
  const { balanceFormatted } = useBalance()

  return (
    <div>
      <button onClick={() => void login()} disabled={isLoading || isAuthenticated}>
        Sign in with Privana
      </button>
      {isAuthenticated ? <button onClick={() => void refresh()}>Refresh Session</button> : null}
      {isAuthenticated ? <button onClick={() => void logout()}>Logout</button> : null}
      {session ? <p>Signed in as {session.address}</p> : null}
      {error ? <p>{error.message}</p> : null}
      <p>Balance: {balanceFormatted}</p>
    </div>
  )
}
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { useHostedRedirectAuth } from '@oasisprotocol/privana-sdk'

function HostedAuthCallbackPage() {
  const router = useRouter()
  const { completeLogin } = useHostedRedirectAuth()
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    void completeLogin()
      .then((session) => {
        if (!session) {
          setError('No hosted authentication response was found.')
          return
        }
        router.replace('/')
      })
      .catch((err) => {
        setError(err instanceof Error ? err.message : 'Hosted authentication failed.')
      })
  }, [completeLogin, router])

  if (error) return <p>Error: {error}</p>
  return <p>Completing sign-in…</p>
}

In hosted-auth mode:

  • the SDK stores PKCE and state in sessionStorage, then redirects the browser to the hosted /auth/authorize page on the Privana auth origin
  • the hosted auth page signs on the current wallet chain if it is supported, otherwise it switches to the provider networkConfig.chainId
  • the hosted auth page redirects back to your registered callback URL with code / state or error
  • your callback route calls completeLogin() to exchange the code at /auth/token
  • private-read hooks use Authorization: Bearer <access_token> and refresh once through /auth/jwt/refresh on 401

Notes:

  • low-level PrivanaClient.getHostedAuthAuthorizeUrl() still mirrors backend authorize URL support and can build either response mode explicitly.
  • consumer apps must implement a callback route at the exact registered redirect_uri.
  • client_id and exact redirect_uri values must be registered in backend AUTH_CLIENTS.
  • staging end-to-end verification requires that registration on the staging deployment.
  • the standalone localhost popup page used during Firefox debugging was diagnostic only; it is not part of the supported SDK integration path.

Hooks

| Hook | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------ | | useHostedRedirectAuth | Hosted redirect auth for widget apps | | useBalance | Get token balance (available + locked) | | useBatchBalances | Get multiple token balances | | useDeposit | Deposit tokens | | useDepositVerification | Run checkDeposit + status polling against an existing on-chain transfer (used by useDeposit and useFiatOnRamp) | | useFiatOnRamp | Buy crypto via MoonPay; delivered straight to the Privana deposit address (from /on-ramp sub-export) | | useWithdraw | Withdraw tokens | | useLockFunds | Lock funds for a recipient | | useUnlockFunds | Unlock expired locks | | useTransfer | Transfer tokens | | useLockedFunds | Get list of locked funds | | useTotalLockedBalance | Get total locked balance for one token | | useHistory | Get authenticated account activity | | usePendingWithdrawals | Get pending withdrawal requests | | useExpiredLocks | Get expired locks that can be claimed | | useTokenList | List all registered tokens | | useTokenInfo | Get info for a single token |

Fiat On-Ramp

The fiat on-ramp lets users buy tokens with a card and have them credited to their Privana balance in one flow. MoonPay delivers the purchased token directly to the user's Privana deposit address.

Exported from a separate entry point so consumers who don't use the on-ramp don't pay the bundle cost of @moonpay/moonpay-react:

import { FiatOnRampForm, useFiatOnRamp } from '@oasisprotocol/privana-sdk/on-ramp'

Setup

The SDK ships @moonpay/moonpay-react as a regular dependency, so it lands in your node_modules automatically. If you import <MoonPayProvider> directly (see below) you may also want to declare it in your own package.json to keep your dependency surface explicit:

npm install @moonpay/moonpay-react

Wrap your app in <MoonPayProvider> (only on routes that use the on-ramp, to keep MoonPay out of unrelated bundles):

import { MoonPayProvider } from '@moonpay/moonpay-react'
;<MoonPayProvider apiKey={import.meta.env.VITE_MOONPAY_API_KEY} debug={import.meta.env.DEV}>
  {/* on-ramp routes */}
</MoonPayProvider>

Quick start with <FiatOnRampForm>

import { FiatOnRampForm } from '@oasisprotocol/privana-sdk/on-ramp'
;<FiatOnRampForm
  tokenId="0x..." // Privana token id (e.g. USDC on Base)
  currencyCode="usdc_base" // MoonPay currency code; test/live is controlled by the apiKey (pk_test_* → testnet, pk_live_* → mainnet)
  baseCurrencyCode="usd" // optional, defaults to "usd"
  defaultBaseCurrencyAmount="100" // optional pre-fill (MoonPay still lets the user edit)
  onCredited={(txHash) => console.log('credited', txHash)}
  onError={(err) => console.error(err)}
/>

The form:

  • fetches the user's Privana deposit address and passes it to MoonPay as the destination,
  • sets externalCustomerId = address.toLowerCase() so the backend can bind the MoonPay transaction to the SIWE-authenticated user,
  • creates a backend on-ramp intent (POST /onramp/intent) and passes its id to MoonPay as externalTransactionId so the webhook can correlate later,
  • gates the "Buy" button on the configured token's minimum deposit (input-time check) and double-checks the delivered amount before triggering verification,
  • on MoonPay's transaction_created, fire-and-forget calls POST /onramp/{id} with the MoonPay transaction id so the backend can reconcile both ids once the delivery webhook lands,
  • listens for MoonPay's transaction_completed event, waits up to 120s for the backend webhook to surface the on-chain tx hash, then triggers Privana verification (checkDeposit + status polling).

useFiatOnRamp for custom UI

If you need a different shell around the MoonPay widget, use the hook directly and wire MoonPay's widget callbacks yourself:

import { MoonPayBuyWidget } from '@moonpay/moonpay-react'
import { useFiatOnRamp } from '@oasisprotocol/privana-sdk/on-ramp'

const {
  status, // 'idle' | 'awaiting-purchase' | 'awaiting-delivery' | 'verifying' | 'credited' | 'failed'
  activeIntentId, // pass to MoonPay as externalTransactionId
  pending, // recovery list (completed-but-unverified)
  error,
  depositAddress, // pass to MoonPay as walletAddress
  minDepositBaseUnits, // for input validation
  selectedToken, // resolved token config (decimals/symbol for the configured tokenId)
  prepareOnRampIntent, // call before opening the widget
  signUrl, // wire to onUrlSignatureRequested
  handleTransactionCreated, // wire to onTransactionCreated
  handleTransactionCompleted, // wire to onTransactionCompleted
  handleWidgetClosed, // call from onClose / onCloseOverlay
  finishPendingVerification, // call from the recovery CTA
  refreshPending, // manual refresh of the pending list
} = useFiatOnRamp({ tokenId, onCredited, onError })

Pending / recovery

If the user closes the tab between MoonPay completion and verification, the backend has already received the webhook and the row appears in pending. Render the list with a "Finish verification" CTA that calls finishPendingVerification(record) — no wallet signature required, just the verification poll.

Required backend endpoints

The useFiatOnRamp hook + form call:

  • POST /v1/accounting/onramp/sign-url — HMAC-signs the MoonPay widget URL
  • POST /v1/accounting/onramp/intent — creates the Privana intent row that ties a MoonPay transaction to the SIWE'd user + Privana token
  • POST /v1/accounting/onramp/{transaction_id} — upserts MoonPay transaction metadata (fire-and-forget on transaction_created)
  • GET /v1/accounting/onramp/pending — completed MoonPay txs awaiting verification

And the existing deposit verification endpoints:

  • POST /v1/accounting/deposits/check
  • GET /v1/accounting/deposits/status/{id}

MoonPay → backend webhook (POST /v1/accounting/onramp/webhook) is what populates the pending list with the on-chain tx hash. Configure that URL in your MoonPay dashboard.

License

Apache-2.0