Payy Network is an EVM-compatible network with a native stablecoin (PUSD). Because it’s EVM-compatible, you can use Privy’s React SDK to authenticate users, create embedded wallets, and sign or send transactions on Payy with minimal configuration.
This recipe shows how to:
Configure Privy for the Payy Network testnet
Create an embedded wallet on Payy
Sign messages, typed data, and transactions
Send native PUSD transfers
Prerequisites
Install dependencies
npm install @privy-io/react-auth @privy-io/chains viem
Step 1: Define the Payy chain
Use viem’s defineChain to create a custom chain definition for Payy Network, then pass it to addRpcUrlOverrideToChain from @privy-io/chains so Privy routes requests through the Payy RPC endpoint.
import { defineChain } from 'viem' ;
import { addRpcUrlOverrideToChain } from '@privy-io/chains' ;
const payyTestnet = addRpcUrlOverrideToChain (
defineChain ({
id: 7298 ,
name: 'Payy Network Testnet' ,
network: 'payy-testnet' ,
nativeCurrency: {
decimals: 18 ,
name: 'Payy USD' ,
symbol: 'PUSD'
},
rpcUrls: {
default: {
http: [ 'https://rpc.testnet.payy.network' ]
}
},
blockExplorers: {
default: {
name: 'Payy Blockscout' ,
url: 'https://blockscout.testnet.payy.network'
}
},
testnet: true
}),
'https://rpc.testnet.payy.network'
);
Set Payy as the default and only supported chain in your PrivyProvider. This constrains embedded wallets to operate exclusively on Payy Network.
import { PrivyProvider } from '@privy-io/react-auth' ;
createRoot ( document . getElementById ( 'root' ) ! ). render (
< StrictMode >
< PrivyProvider
appId = { import . meta . env . VITE_PRIVY_APP_ID ! }
config = { {
defaultChain: payyTestnet ,
supportedChains: [ payyTestnet ]
} }
>
< App />
</ PrivyProvider >
</ StrictMode >
);
Setting supportedChains to only [payyTestnet] ensures Privy creates wallets targeting Payy by
default. If your app supports multiple chains, add them to supportedChains alongside Payy.
Step 3: Authenticate users
Use usePrivy to handle authentication. Users must be authenticated before creating or using a Payy wallet.
import { usePrivy } from '@privy-io/react-auth' ;
function Landing () {
const { ready , authenticated , login , logout } = usePrivy ();
if ( ! ready ) return < div > Loading... </ div > ;
if ( ! authenticated ) {
return < button onClick = { login } > Get started </ button > ;
}
return < button onClick = { logout } > Logout </ button > ;
}
Step 4: Create a Payy wallet
Because Payy is EVM-compatible, use Privy’s standard useCreateWallet hook. The wallet will target Payy Network based on your PrivyProvider configuration.
import { useCreateWallet } from '@privy-io/react-auth' ;
function CreatePayyWallet () {
const { createWallet } = useCreateWallet ({
onSuccess : ({ wallet }) => {
console . log ( 'Created Payy wallet' , wallet . address );
},
onError : ( error ) => {
console . error ( 'Payy wallet creation failed' , error );
}
});
return < button onClick = { () => createWallet ({ createAdditional: true }) } > Create Payy wallet </ button > ;
}
Step 5: Sign messages and transactions
All standard Privy signing hooks work on Payy. Pass the embedded wallet’s address to target it for signing.
Sign a message
import { useSignMessage } from '@privy-io/react-auth' ;
const { signMessage } = useSignMessage ();
const { signature } = await signMessage ({ message: 'Hello from Payy' }, { address: walletAddress });
Sign typed data
import { useSignTypedData } from '@privy-io/react-auth' ;
const { signTypedData } = useSignTypedData ();
const { signature } = await signTypedData (
{
domain: {
name: 'My App' ,
version: '1' ,
chainId: 7298 ,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
},
types: {
Person: [
{ name: 'name' , type: 'string' },
{ name: 'wallet' , type: 'address' }
],
Mail: [
{ name: 'from' , type: 'Person' },
{ name: 'to' , type: 'Person' },
{ name: 'contents' , type: 'string' }
]
},
primaryType: 'Mail' ,
message: {
from: { name: 'Alice' , wallet: walletAddress },
to: { name: 'Bob' , wallet: '0x0000000000000000000000000000000000000001' },
contents: 'Hello from Payy!'
}
},
{ address: walletAddress }
);
Send a transaction
Use useSendTransaction for standard EVM sends on Payy:
import { useSendTransaction } from '@privy-io/react-auth' ;
const { sendTransaction } = useSendTransaction ();
const tx = await sendTransaction ({ to: recipientAddress , value: 0 }, { address: walletAddress });
Step 6: Send PUSD with manual transaction control
For tighter control over nonce, gas, and broadcasting, sign and broadcast transactions manually through the embedded wallet’s Ethereum provider.
This approach is useful when you need custom fee handling, RPC routing through your own backend, or explicit nonce management for PUSD transfers.
import { useWallets } from '@privy-io/react-auth' ;
import { isAddress , parseUnits , toHex } from 'viem' ;
const PAYY_RPC_URL = 'https://rpc.testnet.payy.network' ;
const PUSD_DECIMALS = 18 ;
function SendPUSD () {
const { wallets } = useWallets ();
const handleSend = async ( to : string , amount : string ) => {
const wallet = wallets . find (
( w ) => w . walletClientType === 'privy' || w . walletClientType === 'privy_v2'
);
if ( ! wallet ) throw new Error ( 'No embedded wallet found' );
if ( ! isAddress ( to )) throw new Error ( 'Invalid address' );
const provider = await wallet . getEthereumProvider ();
const value = parseUnits ( amount , PUSD_DECIMALS );
const feePerGas = 1_000_000_000 n ;
const nonceResponse = await fetch ( PAYY_RPC_URL , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
jsonrpc: '2.0' ,
id: 1 ,
method: 'eth_getTransactionCount' ,
params: [ wallet . address , 'pending' ]
})
});
const { result : nonce } = await nonceResponse . json ();
const rawTx = await provider . request ({
method: 'eth_signTransaction' ,
params: [
{
from: wallet . address ,
to ,
value: toHex ( value ),
chainId: toHex ( 7298 ),
gas: toHex ( 21000 n ),
nonce: nonce || '0x0' ,
type: toHex ( 2 ),
maxFeePerGas: toHex ( feePerGas ),
maxPriorityFeePerGas: toHex ( feePerGas )
}
]
});
const broadcastResponse = await fetch ( PAYY_RPC_URL , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
jsonrpc: '2.0' ,
id: 1 ,
method: 'eth_sendRawTransaction' ,
params: [ rawTx ]
})
});
const broadcastData = await broadcastResponse . json ();
if ( broadcastData . error ) {
throw new Error ( broadcastData . error . message || 'Broadcast failed' );
}
return broadcastData . result ;
};
return null ;
}
This example uses a fixed gas price of 1 gwei for both maxFeePerGas and
maxPriorityFeePerGas. In production, query the network for current gas prices or implement
dynamic fee estimation.
Step 7: Read PUSD balances
Query the Payy RPC endpoint directly for native PUSD balances:
const PAYY_RPC_URL = 'https://rpc.testnet.payy.network' ;
async function getBalance ( address : string ) : Promise < bigint > {
const response = await fetch ( PAYY_RPC_URL , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({
jsonrpc: '2.0' ,
id: 1 ,
method: 'eth_getBalance' ,
params: [ address , 'latest' ]
})
});
const { result } = await response . json ();
return BigInt ( result );
}
Choosing a transaction method
Use case Recommended approach Standard EVM sends useSendTransaction hookCustom nonce or fee handling Manual eth_signTransaction + eth_sendRawTransaction Backend-routed RPC Manual signing with your own RPC proxy Simple message/data signing useSignMessage or useSignTypedData hooks
Payy Network reference
Property Value Chain name Payy Network Testnet Chain ID 7298RPC URL https://rpc.testnet.payy.networkBlock explorer https://blockscout.testnet.payy.networkNative currency PUSD (18 decimals)
Configuring EVM networks Learn how to add custom chains to Privy
Reference implementation Full example repo for Payy + Privy integration