Privy 이전
codemod가 Privy import와 Provider 설정을 바꿉니다. 이후 보고서에 나온 미지원 API를 수정하고 로그인과 결제를 확인합니다. @shard-lab/nc-waas-react는 Privy React SDK와 같은 이름, 같은 모양의 API이고, 서버와 wagmi도 같은 이름으로 있습니다. Privy 지갑 키는 옮기지 않습니다. 이용자는 같은 이메일로 로그인해서 새 Pouchi 지갑을 받습니다.

패키지 import
섹션 제목: “패키지 import”API 이름이 같아서 앱 코드의 import는 패키지 이름만 바뀝니다. codemod도 이 변경을 합니다.
import { PrivyProvider, usePrivy } from "@privy-io/react-auth";import { PrivyProvider, usePrivy } from "@shard-lab/nc-waas-react";Provider에는 apiUrl과 앱의 체인을 더하고, appId에 Pouchi 앱 ID를 넣습니다. Provider 설정
이전 순서
섹션 제목: “이전 순서”배포할 코드에 codemod를 적용하기 전에 옛 Privy 지갑 provider를 열 수 있는 이전용 빌드를 보존합니다. 이용자가 자산을 옮길 때는 그 provider로 서명해야 합니다. 이전 기간이 끝날 때까지 유지하세요. import를 바꾸는 것만으로 자산이 이동하지는 않습니다.
-
codemod로 코드를 옮깁니다. 먼저 dry run으로 바뀔 내용과 보고서를 보고,
--write로 적용합니다.터미널 창 # nc-waas 저장소 루트에서node packages/pouchi-migrate/bin/pouchi-migrate.mjs /path/to/my-app --diff # 아무것도 쓰지 않음node packages/pouchi-migrate/bin/pouchi-migrate.mjs /path/to/my-app --writecodemod 패키지(
@shard-lab/pouchi-migrate)는 배포 준비 중입니다. 지금은 nc-waas 저장소의packages/pouchi-migrate에서 실행합니다. 저장소와 패키지 접근 권한이 없으면 business@pouchi.app으로 요청하세요. -
앱을 설정합니다. 앱 주소와 Privy에서 쓰던 로그인 수단을 Pouchi 앱 설정에 맞춥니다. 앱 설정
-
env 값을 넣고 빌드합니다.
NEXT_PUBLIC_POUCHI_TENANT_ID,NEXT_PUBLIC_POUCHI_API_URL(Vite는VITE_). -
보고서의 항목을 처리합니다. 지원하지 않는 API는 파일:줄과 대안이 보고서에 있습니다. 지원 범위
-
이용자에게 안내합니다. 같은 이메일로 로그인하면 새 지갑이 생기고, 옛 지갑의 자산은 이용자가 직접 옮깁니다. 기존 이용자
codemod 변경 범위
섹션 제목: “codemod 변경 범위”| 대상 | 변경 |
|---|---|
| import | @privy-io/react-auth (/solana, /smart-wallets, /extended-chains, /cards, /farcaster 포함), @privy-io/wagmi, @privy-io/server-auth, @privy-io/node을 @shard-lab/nc-waas-react의 같은 하위 경로로. require()도 바꿈 |
PrivyProvider |
apiUrl과 config.defaultChain(앱의 체인, marooTestnet)을 더함. Pouchi에 없는 로그인 수단은 loginMethods에서 뺌 |
| env | NEXT_PUBLIC_PRIVY_APP_ID를 NEXT_PUBLIC_POUCHI_TENANT_ID로, PRIVY_APP_ID를 POUCHI_TENANT_ID로, PRIVY_APP_SECRET을 POUCHI_TENANT_CREDENTIAL로. 코드와 .env* 파일 모두 |
package.json |
Privy 패키지를 @shard-lab/nc-waas-react 하나로 |
| 개발용 프록시 | --dev-proxy <API 주소>를 주면 Next.js 앱의 /v1을 Pouchi API로 넘김 (로컬 개발 전용) |
보고서는 앱이 쓰는 Privy API마다 상태를 보여 줍니다. ok는 같은 동작, different는 동작하지만 차이가 있음, refused는 같은 이름으로 있어 컴파일되고 호출하면 EIP-1193 4200과 문서 링크로 거절, missing은 Pouchi에 없어 코드를 고쳐야 함입니다. missing이 있으면 종료 코드가 2입니다.
REFUSED src/components/sections/fund-wallet.tsx:5:3 useFundWallet: compiles, rejects with 4200 when called. Instead: send funds to the wallet address (testnets: the faucet) [/docs/privy-migration/#funding]Compatibility score: 40% Every import resolves.Provider 설정
섹션 제목: “Provider 설정”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, embeddedWallets: { ethereum: { createOnLogin: "users-without-wallets" } },appId는 Pouchi 앱 ID입니다. 지갑은 앱의 체인 하나에만 있고, supportedChains의 다른 체인은 무시됩니다. externalWallets, mfa, solana, fundingMethodConfig 같은 Privy 설정은 받기만 하고 무시하며, codemod 보고서에 표시됩니다.
로그인 수단
섹션 제목: “로그인 수단”| Privy | Pouchi |
|---|---|
이메일 (useLoginWithEmail, 모달) |
같음. 헤드리스 sendCode는 Pouchi 시트를 열고, 새 이용자는 그 시트에서 지갑 보호(PIN 또는 패스키)를 마침 |
Google, Apple (useLoginWithOAuth) |
같음. 앱 설정에서 켜고 redirect URI를 등록해야 함. Kakao, Naver도 있음 |
패스키 (useLoginWithPasskey, linkPasskey) |
같음. 패스키만으로 가입(useSignupWithPasskey)은 거절: 이메일이나 소셜로 가입한 뒤 패스키를 더함 |
| SMS, 외부 지갑(SIWE, SIWS), Twitter, Discord, GitHub, Telegram, Farcaster, 게스트 | 없음. 같은 이름의 훅은 있고 호출하면 4200. 이 이용자들은 이메일이나 지원하는 소셜 계정으로 로그인 |
useOAuthTokens |
콜백이 호출되지 않음. 마운트할 때 콘솔에 경고 |
wagmi
섹션 제목: “wagmi”@privy-io/wagmi의 createConfig, WagmiProvider, useSetActiveWallet이 같은 이름으로 @shard-lab/nc-waas-react/wagmi에 있습니다. createConfig는 임베디드 지갑 커넥터와 앱의 체인(첫 번째)을 더하고, WagmiProvider는 로그인하면 wagmi를 지갑에 연결하고 로그아웃하면 끊습니다. 그래서 useAccount, useSignMessage, useSignTypedData, useSendTransaction 같은 wagmi 훅이 그대로 Pouchi 지갑에서 동작합니다. useSetActiveWallet은 임베디드 지갑 하나만 고를 수 있습니다.
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); // claims.userId, claims.walletAddress| Privy | Pouchi |
|---|---|
verifyAuthToken, utils().auth().verifyAuthToken |
같음. JWKS로 오프라인 검증, 비밀값 필요 없음. API 주소는 POUCHI_API_URL |
getUser(id), getUser({ idToken }), users().get |
지갑 주소와 ID를 돌려줌. 이메일은 없음(이메일이 필요하면 아래 조회). idToken 자리에는 access token |
getUserByEmail, getUserByWalletAddress |
같은 이름으로 동작. 이 앱의 이용자만 찾고, 없으면 null. 이메일은 이메일 로그인과 비교(대소문자와 앞뒤 공백 무시). 소셜 계정의 이메일로는 찾지 않음. 이용자 ID, 이메일, 지갑 주소, 상태(ACTIVE_UNPROTECTED 포함), 가입 시각을 돌려줌 |
users().getByEmailAddress({ address }), users().getByWalletAddress({ address }) |
위와 같음. 없으면 Privy처럼 404 오류 |
getUserByPhoneNumber, getUserByCustomAuthId, getUsers, users().list |
거절. 이 정보와 user.id의 연결은 앱 DB에 둠 |
importUser, users().create, deleteUser |
거절. 이용자는 앱에서 가입하고 새 지갑을 받음 |
appSecret 자리에는 앱의 Client Credential을 <credentialId>:<clientSecret> 형태로 넣습니다(wallets:read). getUser와 이용자 조회에 필요합니다. 조회는 Credential마다 분당 횟수 제한이 있고(기본 120회, 넘으면 429), 모두 감사 로그에 남습니다.
verifyWebhook(payload, headers, secret)과 webhooks().verify(...)는 Pouchi 웹훅(x-waas-signature: sha256=<hex>, x-waas-event-id)을 검증하고 이벤트를 돌려줍니다. Pouchi 이벤트 종류는 Privy와 다릅니다. Privy(Svix) 헤더만 있는 요청은 거절합니다.
서버 지갑
섹션 제목: “서버 지갑”walletApi, wallets(), policies(), keyQuorums(), useSigners, useAuthorizationSignature는 모두 거절합니다. Pouchi는 서버에서 이용자 대신 서명하지 않습니다. 앱 안의 반복 승인은 useSessionApproval, 서버가 실행하는 결제는 앱 서버가 실행하는 결제를 씁니다. 두 방식 모두 한도를 지갑 컨트랙트가 강제합니다.
지원 범위
섹션 제목: “지원 범위”| Privy API | 상태 | 대안 |
|---|---|---|
usePrivy, useLogin, useLogout, useUser, useToken, getAccessToken, useModalStatus |
지원 | user.id는 did:nc-waas:<tenant>:<walletId> |
useWallets, useCreateWallet |
지원 | 현재 앱에서 이용자당 EVM 스마트 지갑 1개. createAdditional은 거절 |
useSendTransaction, useSendCalls, useSmartWallets, SmartWalletsProvider |
지원 | ERC-4337. 가스비는 앱이 부담하며 이용자 지갑에서는 차감하지 않음 |
useSignMessage, useSignTypedData, toViemAccount |
지원 | ERC-1271 (배포 전 지갑은 ERC-6492). 타입 데이터의 chainId는 앱의 체인 |
useLinkAccount, useLinkWithPasskey |
지원 | 이메일, Google, Apple, Kakao, Naver, 패스키. 나머지는 onError로 거절 |
usePrivy().exportWallet |
다르게 동작 | 키가 나뉘어 있어 내보내지 않음. 계정 복구 수단 안내 |
useSessionSigners, useSigners, useAuthorizationSignature |
거절 (NOT_SUPPORTED) |
앱 안 반복 결제는 useSessionApproval, 서버 실행은 승인된 위임 결제 |
useIdentityToken |
다르게 동작 | identityToken은 null. access token이 이용자를 증명 |
useSignTransaction, eth_sign |
거절 | 스마트 지갑은 서명된 원시 트랜잭션이 없음. sendTransaction |
useFundWallet, useAddFunds, useFiatOnramp, useDepositAddress, /cards |
거절 | 지갑 주소로 직접 입금 (테스트넷은 faucet) |
useImportWallet, setWalletPassword |
거절 | 개인키를 받지 않음. 키는 처음부터 나뉘어 생성 |
useMfaEnrollment, useMfa |
거절 | 승인에는 사용자 인증(패스키, PIN 등)을 사용 |
useCrossAppAccounts, @privy-io/cross-app-* |
거절, 없음 | 앱마다 Pouchi로 로그인 |
/solana, /extended-chains, useLoginWithSiws |
거절 | EVM 전용. 지갑 목록은 비어 있음 |
@privy-io/expo, @privy-io/js-sdk-core |
없음 | React Native SDK는 아직 없음. 프레임워크 없이는 @shard-lab/nc-waas-react/core |
거절은 조용히 넘어가지 않습니다. 같은 이름과 타입이 있어 코드는 컴파일되고, 호출하면 NcWaasError (code: "NOT_SUPPORTED", EIP-1193 4200)가 이유와 이 페이지의 링크를 담아 실패합니다.
앱 설정
섹션 제목: “앱 설정”node packages/pouchi-migrate/bin/pouchi-migrate.mjs tenant /path/to/my-app --tenant <앱 ID> --api-url <Pouchi API> --origin https://app.example.com읽기만 합니다. 앱의 loginMethods를 Pouchi 로그인 수단에 맞춰 보여 주고, 앱 주소가 이미 허용됐는지(API의 CORS 응답) 확인하고, env 값과 할 일을 출력합니다. 적용은 Pouchi 콘솔의 설정 화면이나 개발자 MCP의 set_app_origins, set_login_methods로 합니다. AI 개발 도구 연동
| 할 일 | 어디서 |
|---|---|
| 앱 주소 등록 (CORS, 패스키) | 콘솔 설정 또는 set_app_origins |
| 이메일 로그인, Google, Apple 켜기 | 콘솔 설정 또는 set_login_methods. 소셜 로그인은 운영자가 먼저 제공자를 설정 |
| 가스 대납 한도 | 콘솔 또는 set_sponsorship |
서버의 getUser용 Client Credential (wallets:read) |
콘솔 |
기존 이용자
섹션 제목: “기존 이용자”-
같은 이메일로 로그인합니다. 이용자는 Privy에서 쓰던 이메일(또는 Google, Apple)로 로그인하고, 첫 로그인 때 새 Pouchi 지갑을 받습니다. SMS나 외부 지갑으로만 로그인하던 이용자는 이메일을 먼저 등록하게 안내합니다.
-
필요하면 두 계정을 연결합니다. 같은 요청에 Privy 토큰과 Pouchi 토큰을 함께 보내고 서버에서 둘 다 검증한 뒤 연결합니다. 이메일이 같다는 이유만으로 연결하지 않습니다.
import { createNcWaasServerClient, linkPrivyUser } from "@shard-lab/nc-waas-react/server";const privyClaims = await privyClient.verifyAuthToken(privyToken); // Privy 서버 SDK (이전 기간 동안만)const ncWaasClaims = await pouchi.verifyAuthToken(pouchiToken);await db.userLinks.insert(linkPrivyUser({ privyClaims, ncWaasClaims, oldAddress })); -
자산은 이용자가 옮깁니다(선택). 옛 Privy 지갑에서 새 Pouchi 주소로 보내는 보통의 전송이고, 이용자가 아직 Privy를 쓰는 앱 화면에서 직접 서명합니다.
AssetMigrationCard가 토큰을 먼저, 네이티브 코인은 가스 여유분을 남기고 마지막에 보냅니다. 같은 체인에서 보내는 전송이며 브리지가 아닙니다. 현재 절차는 Maroo Testnet(450815)의 테스트 자산만 대상으로 합니다. 보내기 전에 두 지갑과 자산이 지원 체인에 있는지 확인하세요. 다른 체인에서 같은 주소로 보내도 Pouchi에서 쓸 수 있게 되지는 않습니다. 옛 Privy 지갑의 전송 가스비는 그 지갑에서 준비해야 합니다.import { AssetMigrationCard } from "@shard-lab/nc-waas-react";const oldProvider = await privyWallet.getEthereumProvider();<AssetMigrationCard oldProvider={oldProvider} tokens={[{ address: TOKEN, symbol: "tKRW", decimals: 18 }]} /> -
주소에 묶인 데이터를 갱신합니다. 앱 DB와 허용 목록이 새 주소를 가리키도록 갱신합니다. NFT 소유권은 지원 체인에서 별도로 전송해야 하며 DB 주소를 바꿔도 이동하지 않습니다. DeFi 포지션, 토큰 사용 승인과 ENS 이름은 자동으로 따라오지 않습니다.
-
이전 기간이 끝나면 Privy SDK를 지웁니다. 옮기지 않은 이용자에게는 Privy의 지갑 내보내기 안내를 남깁니다.
이전 후 확인
섹션 제목: “이전 후 확인”codemod를 적용하고 앱을 실행한 뒤 아래 순서로 확인합니다.
-
로그인 버튼을 누릅니다. Privy 모달 대신 Pouchi 로그인 시트가 열립니다.
-
같은 이메일로 로그인합니다. 처음이면 지갑 보호(PIN이나 패스키)를 설정하고 새 지갑 주소가 생깁니다.
-
송금이나 서명을 한 번 실행합니다. Pouchi 승인 화면이 열리고, 승인하면 기존 코드가 받던 값(
hash, 서명)이 그대로 돌아옵니다.
자주 겪는 오류
섹션 제목: “자주 겪는 오류”| 증상 | 원인 | 해결 |
|---|---|---|
| codemod가 종료 코드 2로 끝남 | 보고서에 missing(Pouchi에 없는 API)이 있어 타입 검사가 통과하지 않음 |
보고서의 파일:줄과 대안대로 코드를 고침. 지원 범위 |
호출하면 NOT_SUPPORTED (rpcCode 4200) |
useFundWallet처럼 거절하는 API를 호출함 |
오류 메시지의 링크와 지원 범위의 대안을 씀 |
INVALID_PARAMS: NcWaasProvider needs chain (or config.defaultChain) |
손으로 옮기면서 체인을 빠뜨림 | config.defaultChain: marooTestnet을 더함 |
| 브라우저 콘솔에 API 요청의 CORS 오류 | 앱 주소가 Pouchi 앱 설정의 허용 출처에 없음 | pouchi-migrate tenant로 허용 여부를 보고 콘솔이나 set_app_origins로 등록 |
서버의 getUser가 NOT_SUPPORTED: this call needs a tenant Client Credential |
appSecret 자리에 Client Credential이 없음 |
POUCHI_TENANT_CREDENTIAL에 <credentialId>:<clientSecret>을 넣음 (wallets:read) |
| 기존 이용자의 지갑 주소가 다름 | 정상. Privy 키는 옮기지 않아 새 지갑이 생김 | 기존 이용자 순서대로 계정 연결과 자산 이동을 안내 |
공식 예제 호환 점수
섹션 제목: “공식 예제 호환 점수”privy-io/examples 커밋 95a0f6e의 예제 전체에 같은 이전을 해 본 결과입니다. “기존 SDK”는 import 교체와 Provider 설정만 한 코드를 이번 작업 전 SDK로 타입 검사한 결과, “codemod 후”는 pouchi-migrate --write 결과를 지금 SDK로 타입 검사한 결과입니다(원본에 이미 있던 오류는 뺌). 점수는 예제가 쓰는 Privy API마다 같은 동작 1, 다르게 동작 0.75, 거절이나 없음 0으로 매긴 평균입니다. Privy 기능을 모두 보여 주는 스타터는 Solana, 충전, MFA 같은 화면이 많아 점수가 낮고, 실제 앱에 가까운 예제가 높습니다.
| 예제 | Privy 패키지 | 기존 SDK | codemod 후 | 빌드 | 실행 | 점수 | 남는 차이 |
|---|---|---|---|---|---|---|---|
| privy-next-cards | react-auth | 실패 (오류 6) | 통과 | n/a | n/a | 89% | 카드 |
| privy-next-permissionless | react-auth | 실패 (오류 2) | 통과 | n/a | n/a | 88% | 없음 |
| privy-next-fiat-orchestration | node, react-auth | 실패 (오류 2) | 통과 | n/a | n/a | 84% | 서버 서명자 |
| privy-react-pwa | react-auth | 실패 (오류 3) | 통과 | n/a | n/a | 83% | 없음 |
| privy-next-fiat-onramp | react-auth, server-auth | 실패 (오류 1) | 통과 | 성공 | n/a | 81% | 이용자 삭제, Solana |
| privy-next-yield-demo | react-auth, server-auth | 실패 (오류 9) | 통과 | n/a | n/a | 77% | 서버 서명자, 일부 로그인 수단 |
| privy-next-tempo | node, react-auth | 실패 (오류 3) | 통과 | n/a | n/a | 67% | 전화번호로 이용자 조회, 이용자 생성, 일부 로그인 수단 |
| privy-react-whitelabel-starter | react-auth | 실패 (오류 46) | 통과 | 성공 | 라이브 | 56% | Solana, 일부 로그인 수단 |
| privy-next-cross-app-connect | cross-app-connect, react-auth | 통과 | 통과 (Privy 패키지 남음) | n/a | n/a | 50% | cross-app-connect (Privy 전용) |
| privy-next-solana | react-auth | 실패 (오류 2) | 통과 | n/a | n/a | 42% | Solana, 원시 트랜잭션 서명, 충전, 크로스 앱 계정 |
| privy-next-cross-app-provider | cross-app-provider, react-auth | 실패 (오류 2) | 통과 (Privy 패키지 남음) | n/a | n/a | 41% | cross-app-provider (Privy 전용), 서버 서명자, Solana, 충전 |
| privy-next-smart-wallets | react-auth | 실패 (오류 2) | 통과 | n/a | n/a | 41% | 서버 서명자, Solana, 원시 트랜잭션 서명, 충전 |
| privy-next-wagmi | react-auth, wagmi | 실패 (오류 7) | 통과 | 성공 | 라이브, 영상 | 40% | 서버 서명자, Solana, 원시 트랜잭션 서명, 충전 |
| privy-next-starter | react-auth | 통과 | 통과 | 성공 | n/a | 39% | 서버 서명자, Solana, 원시 트랜잭션 서명, 충전 |
| privy-react-starter | react-auth | 통과 | 통과 | 성공 | n/a | 39% | 서버 서명자, Solana, 원시 트랜잭션 서명, 충전 |
| privy-react-chrome-extension | react-auth | 통과 | 통과 | n/a | n/a | 39% | 서버 서명자, Solana, 원시 트랜잭션 서명, 충전 |
| privy-next-farcaster | react-auth | 실패 (오류 6) | 통과 | n/a | n/a | 39% | Farcaster, 서버 서명자, Solana, 충전 |
| privy-next-session-keys | react-auth | 실패 (오류 1) | 통과 | n/a | n/a | 39% | 서버 서명자, Solana, 원시 트랜잭션 서명, 충전 |
| privy-next-farcaster-mini-app | react-auth | 실패 (오류 1) | 통과 | n/a | n/a | 38% | Farcaster, 서버 서명자, Solana, 충전 |
| privy-next-funding | react-auth | 실패 (오류 4) | 통과 | n/a | n/a | 37% | 충전, 서버 서명자, Solana |
| privy-node-telegram-trading-bot | server-auth | 통과 | 통과 | n/a | n/a | 19% | 서버 지갑 |
| privy-node-starter | node | 실패 (오류 1) | 통과 | n/a | n/a | 13% | 서버 지갑 |
| privy-next-mpp-agent-demo | node | 실패 (오류 1) | 통과 | n/a | n/a | 11% | 서버 지갑 |
| privy-next-x402-agent-demo | node | 실패 (오류 1) | 실패 (오류 1) | n/a | n/a | 9% | node/x402 (Privy 전용), 서버 지갑 |
| privy-vanilla-starter | js-sdk-core | 통과 | 통과 (Privy 패키지 남음) | n/a | n/a | 0% | js-sdk-core (Privy 전용) |
| privy-expo-starter, privy-expo-bare-starter | expo | 대상 아님 | 대상 아님 | n/a | n/a | n/a | React Native |
| privy-flutter-starter, privy-swift-auth0 | 네이티브 | 대상 아님 | 대상 아님 | n/a | n/a | n/a | 웹 SDK 대상이 아님 |
웹과 서버 예제 25개 중 24개가 codemod 후 타입 검사를 통과합니다(기존 SDK로는 19개 실패). 그중 21개는 Privy 패키지가 하나도 남지 않습니다. 빌드는 다섯 개(privy-next-starter, privy-next-wagmi, privy-react-starter, privy-react-whitelabel-starter, privy-next-fiat-onramp) 모두 성공했고, wagmi와 whitelabel 예제는 로컬 Pouchi API와 Maroo Testnet에서 로그인, 서명, 송금까지 실제로 실행했습니다.
다음 단계
섹션 제목: “다음 단계”- 빠른 시작: 송금, 서버 검증, wagmi 연결
- 위임 결제: Privy 서버 지갑 대신 쓰는 한도 승인
- React SDK 레퍼런스: 주요 공개 API와 지원 상태
- AI 개발 도구 연동: 코딩 에이전트에 이전 작업 맡기기