Skip to content
PricingDocsKO

Privy migration

The codemod updates Privy imports and provider settings. Then address unsupported APIs in its report and verify sign-in and payments. @shard-lab/nc-waas-react has the Privy React SDK’s names and shapes, and the server and wagmi entries keep Privy’s names too. Privy wallet keys are not moved: users sign in with the same email and get a new Pouchi wallet.

Pouchi sign-in
Pouchi sign-in opened by Ticketly (390px, English UI, October 20, 2026).

The API names match, so the imports in your app code change only the package name. The codemod makes this change too.

import { PrivyProvider, usePrivy } from "@privy-io/react-auth";
import { PrivyProvider, usePrivy } from "@shard-lab/nc-waas-react";

The provider also gets apiUrl and the app’s chain, and appId takes your Pouchi app ID. Provider setup

Before applying the codemod to your release build, preserve a transition build that can still open the old Privy wallet provider. Users need that provider to sign any asset transfers. Keep it available until the migration period ends; changing package imports alone does not move funds.

  1. Move the code with the codemod. Look at the dry run and its report first, then apply with --write.

    Terminal window
    # from the nc-waas repository root
    node packages/pouchi-migrate/bin/pouchi-migrate.mjs /path/to/my-app --diff # writes nothing
    node packages/pouchi-migrate/bin/pouchi-migrate.mjs /path/to/my-app --write

    The codemod package (@shard-lab/pouchi-migrate) is not published yet. Today you run it from packages/pouchi-migrate in the nc-waas repository. Request repository and package access from business@pouchi.app if you do not have them.

  2. Set up the app. Register the app origins and the login methods you used on Privy. App setup

  3. Set the env values and build. NEXT_PUBLIC_POUCHI_TENANT_ID, NEXT_PUBLIC_POUCHI_API_URL (VITE_ for Vite).

  4. Work through the report. Every API Pouchi does not offer is listed with file:line and the replacement. Support

  5. Tell your users. The same email signs in to a new wallet; users move funds from the old wallet themselves. Existing users

Target Change
Imports @privy-io/react-auth (with /solana, /smart-wallets, /extended-chains, /cards, /farcaster), @privy-io/wagmi, @privy-io/server-auth and @privy-io/node to the same subpaths of @shard-lab/nc-waas-react, require() included
PrivyProvider Adds apiUrl and config.defaultChain (the app’s chain, marooTestnet). Removes login methods Pouchi has no counterpart for
Env NEXT_PUBLIC_PRIVY_APP_ID to NEXT_PUBLIC_POUCHI_TENANT_ID, PRIVY_APP_ID to POUCHI_TENANT_ID, PRIVY_APP_SECRET to POUCHI_TENANT_CREDENTIAL, in code and .env* files
package.json The Privy packages become @shard-lab/nc-waas-react
Dev proxy --dev-proxy <API origin> makes a Next.js app forward /v1 to the Pouchi API (local development only)

The report rules each Privy API the app uses: ok (same behavior), different (works, with a difference), refused (same name and type, so it compiles; a call fails with EIP-1193 4200 and a docs link) or missing (not in Pouchi: the code must change). The exit code is 2 when something is missing.

import { PrivyProvider } from "@privy-io/react-auth";
import { PrivyProvider, marooTestnet } from "@shard-lab/nc-waas-react";
<PrivyProvider
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID!}
apiUrl={process.env.NEXT_PUBLIC_POUCHI_API_URL!}
appId={process.env.NEXT_PUBLIC_POUCHI_TENANT_ID!}
config={{
defaultChain: marooTestnet,

appId is the Pouchi app ID. The wallet lives on the app’s chain only; other supportedChains are ignored. Privy settings such as externalWallets, mfa, solana and fundingMethodConfig are accepted, ignored, and listed in the report.

Privy Pouchi
Email (useLoginWithEmail, modal) Same. The headless sendCode opens the Pouchi sheet, where a new user also protects the wallet (PIN or passkey)
Google, Apple (useLoginWithOAuth) Supported after you enable the providers in app settings and register the redirect URI. Kakao and Naver too
Passkey (useLoginWithPasskey, linkPasskey) Same. Passkey-only sign-up is refused: sign up with email or social login, then add a passkey
SMS, external wallets (SIWE, SIWS), Twitter, Discord, GitHub, Telegram, Farcaster, guests Not offered. The hooks exist and a call fails with 4200; these users sign in with email or a supported social account
useOAuthTokens The callback never fires; a console warning says so on mount

createConfig, WagmiProvider and useSetActiveWallet from @privy-io/wagmi are in @shard-lab/nc-waas-react/wagmi. createConfig adds the embedded wallet connector and the app’s chain (first); WagmiProvider connects wagmi to the wallet on sign-in and disconnects on sign-out, so useAccount, useSignMessage, useSignTypedData and useSendTransaction act on the Pouchi wallet. useSetActiveWallet selects the one embedded wallet.

import { PrivyClient } from "@privy-io/server-auth";
import { PrivyClient } from "@shard-lab/nc-waas-react/server";
const privy = new PrivyClient(process.env.POUCHI_TENANT_ID!, process.env.POUCHI_TENANT_CREDENTIAL);
const claims = await privy.verifyAuthToken(accessToken);
Privy Pouchi
verifyAuthToken, utils().auth().verifyAuthToken Same. Offline JWKS verification, no secret needed. API origin from POUCHI_API_URL
getUser(id), getUser({ idToken }), users().get Returns the wallet and the user id, without the email (use the lookups below for it). Pass the access token as idToken
getUserByEmail, getUserByWalletAddress Same names. Finds only this app’s users and returns null when none matches. The email is compared with the email login (case and surrounding spaces ignored); a social account’s email is not matched. Returns the user id, email, wallet address, status (including ACTIVE_UNPROTECTED) and sign-up time
users().getByEmailAddress({ address }), users().getByWalletAddress({ address }) As above; a missing user is a 404 error, as in Privy
getUserByPhoneNumber, getUserByCustomAuthId, getUsers, users().list Refused. Keep that mapping to user.id in your database
importUser, users().create, deleteUser Refused. Users sign up in your app and get a new wallet

appSecret is the app’s Client Credential written as <credentialId>:<clientSecret> (wallets:read), needed by getUser and the user lookups. Lookups are rate limited per credential (120 per minute by default, then 429) and every lookup is audited.

verifyWebhook(payload, headers, secret) and webhooks().verify(...) check a Pouchi webhook (x-waas-signature: sha256=<hex>, x-waas-event-id) and return the event. Pouchi event types differ from Privy’s. A request with only Privy’s Svix headers is refused.

walletApi, wallets(), policies(), keyQuorums(), useSigners and useAuthorizationSignature are refused: Pouchi never signs for a user on the server. Use useSessionApproval inside the app, or app-server payments for payments the server runs; the wallet contract enforces the limits of both.

Privy API Status Instead
usePrivy, useLogin, useLogout, useUser, useToken, getAccessToken, useModalStatus Supported user.id is did:nc-waas:<tenant>:<walletId>
useWallets, useCreateWallet Supported One EVM smart wallet per user in the current app; createAdditional is refused
useSendTransaction, useSendCalls, useSmartWallets, SmartWalletsProvider Supported ERC-4337; the app pays gas; the user’s wallet is not charged
useSignMessage, useSignTypedData, toViemAccount Supported ERC-1271 (ERC-6492 before deployment); typed data chainId is the app’s chain
useLinkAccount, useLinkWithPasskey Supported Email, Google, Apple, Kakao, Naver, passkey; others report through onError
usePrivy().exportWallet Different The split key is not exported; the account recovery method is explained instead
useSessionSigners, useSigners, useAuthorizationSignature Rejected (NOT_SUPPORTED) Use useSessionApproval in the app, or an approved delegated policy for server execution
useIdentityToken Different identityToken is null; the access token identifies the user
useSignTransaction, eth_sign Refused A smart wallet has no raw signed transaction; use sendTransaction
useFundWallet, useAddFunds, useFiatOnramp, useDepositAddress, /cards Refused Fund the wallet address directly (testnets: the faucet)
useImportWallet, setWalletPassword Refused No private key is ever taken; keys are created split
useMfaEnrollment, useMfa Refused Every approval already needs the device PIN or a passkey
useCrossAppAccounts, @privy-io/cross-app-* Refused, missing Each app signs its users in with Pouchi
/solana, /extended-chains, useLoginWithSiws Refused EVM only; wallet lists are empty
@privy-io/expo, @privy-io/js-sdk-core Missing No React Native SDK yet; without a framework use @shard-lab/nc-waas-react/core
Terminal window
node packages/pouchi-migrate/bin/pouchi-migrate.mjs tenant /path/to/my-app --tenant <app id> --api-url <Pouchi API> --origin https://app.example.com

Read-only: maps the app’s loginMethods to Pouchi’s, checks which app origins the API already allows (its CORS answer), and prints the env values and what to apply in the Pouchi console or with the developer MCP tools set_app_origins and set_login_methods.

  1. Same email, new wallet. Users sign in with the email (or Google, Apple) they used on Privy and get a new Pouchi wallet at first login. Users who only had SMS or an external wallet add an email first.

  2. Link the accounts if you need to. Send the Privy token and the Pouchi token in the same request, verify both on the server, then link them (linkPrivyUser in /server). Never link on a matching email alone.

  3. Move funds (optional). A normal transfer from the old Privy wallet to the new Pouchi address that the user signs in the app while it still has Privy. AssetMigrationCard sends tokens first and the native coin last, keeping a gas reserve. This is a same-chain transfer, not a bridge. The current walkthrough supports Maroo Testnet (450815) test assets only. Check both wallets and the asset on that supported chain before sending; sending assets on another chain does not make them usable in Pouchi. The old Privy wallet must cover its own transfer gas.

  4. Update address-bound data. Update your database and allow lists to reference the new address. NFT ownership requires a separate on-chain transfer on a supported chain; changing a database record does not transfer it. DeFi positions, token approvals and ENS names do not follow automatically.

  5. Remove the Privy SDK after the transition; point users who did not move to Privy’s wallet export.

After applying the codemod, run the app and check in this order.

  1. Press the sign-in button. The Pouchi sign-in sheet opens instead of the Privy modal.

  2. Sign in with the same email. The first time, the user sets wallet protection (PIN or passkey) and gets a new wallet address.

  3. Run one transfer or signature. The Pouchi approval screen opens, and on approval your existing code gets the same values back (hash, signature).

Symptom Cause Fix
The codemod exits with code 2 The report has missing APIs (not in Pouchi), so type checking fails Fix the code at the file:line and with the alternative in the report. Support
A call fails with NOT_SUPPORTED (rpcCode 4200) A refused API such as useFundWallet was called Use the link in the error message and the alternative under Support
INVALID_PARAMS: NcWaasProvider needs chain (or config.defaultChain) The chain was dropped in a manual migration Add config.defaultChain: marooTestnet
CORS error for API requests in the browser console The app origin is not in the Pouchi app’s allowed origins Check with pouchi-migrate tenant, then register it in the console or with set_app_origins
Server getUser fails with NOT_SUPPORTED: this call needs a tenant Client Credential No Client Credential in place of appSecret Set POUCHI_TENANT_CREDENTIAL to <credentialId>:<clientSecret> (wallets:read)
An existing user has a different wallet address Expected. Privy keys are not moved, so a new wallet is created Follow Existing users to link accounts and move assets

All of privy-io/examples at 95a0f6e went through the same migration. Of the 25 web and server examples, 24 type-check after the codemod (19 failed with the SDK before this work) and 21 keep no Privy package. Five built (privy-next-starter, privy-next-wagmi, privy-react-starter, privy-react-whitelabel-starter, privy-next-fiat-onramp), and the wagmi and whitelabel examples ran live against a local Pouchi API on Maroo Testnet: login, signing and a transfer. The score per example (same behavior 1, different 0.75, refused or missing 0) is on the Korean page.