Skip to main content
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'
);

Step 2: Configure PrivyProvider

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_000n;

    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(21000n),
          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 caseRecommended approach
Standard EVM sendsuseSendTransaction hook
Custom nonce or fee handlingManual eth_signTransaction + eth_sendRawTransaction
Backend-routed RPCManual signing with your own RPC proxy
Simple message/data signinguseSignMessage or useSignTypedData hooks

Payy Network reference

PropertyValue
Chain namePayy Network Testnet
Chain ID7298
RPC URLhttps://rpc.testnet.payy.network
Block explorerhttps://blockscout.testnet.payy.network
Native currencyPUSD (18 decimals)

Configuring EVM networks

Learn how to add custom chains to Privy

Reference implementation

Full example repo for Payy + Privy integration