Conversation
Server-side (nodejs) and client-side (react) Privy wallet examples for light-token transfers, wrap, unwrap, load, and balances. Restructured privy/ layout: - nodejs/: server wallet signing via @privy-io/node - react/: embedded wallet signing via @privy-io/react-auth - scripts/: setup helpers (mint creation, SPL interface registration) Transfer creates destination associated token account idempotently before transferring.
React hooks (useTransfer, useUnwrap) now call high-level SDK functions (createTransferInterfaceInstructions, createUnwrapInstructions) instead of manually assembling instructions. Shared signAndSendBatches helper handles multi-tx flows from batched instruction arrays. Balance hooks (react + nodejs) refactored to per-mint accumulator pattern that queries all mints in one pass instead of single-mint. TokenBalance now exposes hot/cold/spl/t22/unified breakdown. Other changes: - useWrap: imports from /unified subpath, uses idempotent ATA create - unwrap (nodejs + react): auto-detect token program from mint owner - mint-light-token script: --spl flag for SPL token program mints - mint-spl script: auto-detect token program from mint account - Remove standalone load.ts (load is handled by transfer/unwrap SDK)
Remove hidden wrap-then-transfer flow from Send button. Add explicit Unify button for wrapping SPL/T22 into unified balance. Send only enabled when unified balance > 0.
| const getOrCreate = (mintStr: string) => { | ||
| let entry = mintMap.get(mintStr); | ||
| if (!entry) { | ||
| entry = { spl: 0n, t22: 0n, hot: 0n, cold: 0n, decimals: 9 }; |
There was a problem hiding this comment.
🔴 Hardcoded decimals: 9 in React balance hook causes incorrect transfer amounts for non-9-decimal tokens
The useLightTokenBalances hook hardcodes decimals: 9 for every token entry. This value propagates through the transfer flow and is used to compute the raw token amount sent on-chain.
Root Cause and Impact
At toolkits/sign-with-privy/react/src/hooks/useLightTokenBalances.ts:39, all mint entries are created with decimals: 9:
entry = { spl: 0n, t22: 0n, hot: 0n, cold: 0n, decimals: 9 };This decimals field flows through TransferForm.tsx:76 into useTransfer.ts:42:
const tokenAmount = Math.floor(amount * Math.pow(10, decimals));For a token with 6 decimals (e.g., USDC — explicitly mentioned in the README as a wrapping example), when a user enters amount 1:
- Actual:
tokenAmount = 1 * 10^9 = 1,000,000,000 - Expected:
tokenAmount = 1 * 10^6 = 1,000,000
This sends 1000x more tokens than the user intended. The same hardcoded decimals also causes formatBigint(total, balance.decimals) in TransferForm.tsx:147 to display balances at the wrong scale.
Impact: Users transferring any token with decimals ≠ 9 will send a drastically wrong amount, potentially draining their balance.
Prompt for agents
In toolkits/sign-with-privy/react/src/hooks/useLightTokenBalances.ts, the decimals field is hardcoded to 9 on line 39. The actual decimals should be fetched from the on-chain mint account data (stored at byte offset 44 in the SPL mint layout) or from the getAtaInterface parsed response. One approach: after populating the mintMap, fetch each mint's account info via rpc.getAccountInfo(mint) and parse the decimals byte at offset 44 of the data buffer. Then set entry.decimals to the parsed value. The same hardcoded decimals issue exists in the Node.js balances.ts at toolkits/sign-with-privy/nodejs/src/balances.ts line 45.
Was this helpful? React with 👍 or 👎 to provide feedback.
| signatures.push(sig); | ||
| } | ||
|
|
||
| return signatures[signatures.length - 1]; |
There was a problem hiding this comment.
🟡 Node.js transfer.ts and unwrap.ts return undefined when SDK returns empty instruction batches
When createTransferInterfaceInstructions or createUnwrapInstructions returns an empty array, the functions return undefined instead of a string signature.
Detailed Explanation
In toolkits/sign-with-privy/nodejs/src/transfer.ts:60:
return signatures[signatures.length - 1];If instructions (from createTransferInterfaceInstructions) is [], the for loop never executes, signatures stays empty, and signatures[-1] evaluates to undefined. The same pattern exists in toolkits/sign-with-privy/nodejs/src/unwrap.ts:68.
The React equivalent (toolkits/sign-with-privy/react/src/hooks/signAndSendBatches.ts:43) correctly handles this:
return signatures.length > 0 ? signatures[signatures.length - 1] : null;Impact: The caller at line 75 logs Transfer signature: undefined, and any downstream code relying on a valid signature string will break silently.
| return signatures[signatures.length - 1]; | |
| if (signatures.length === 0) throw new Error('Transfer returned no instructions'); | |
| return signatures[signatures.length - 1]; |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
toolkits/sign-with-privy/with Node.js and React examples for light-token operations signed via Privy wallets (devnet)Test plan
npm installresolves workspacesnpm run balancesintoolkits/sign-with-privy/nodejs/)npm run devintoolkits/sign-with-privy/react/)🤖 Generated with Claude Code