Comprehensive reference for how TicketChain integrates Push Chain for wallet connectivity, cross-chain ticketing, and marketplace operations.
| Layer | Responsibility | Key Files |
|---|---|---|
| UI Shell | Initializes Push Chain universal wallet, wraps React tree | frontend/ticketchain/src/main.tsx, src/providers/PushChainProvider.tsx |
| Wallet Context | Exposes connection state, universal accounts, helper hooks | @pushchain/ui-kit (usePushWalletContext, usePushChainClient, usePushChain, PushUniversalAccountButton) |
| Read Operations | Uses wagmi + JSON RPC provider for contract reads | src/lib/wagmi.ts, src/lib/pushchain.ts, src/hooks/useContracts.ts |
| Write Operations | Sends cross-chain transactions through Push universal accounts | src/hooks/useContracts.ts, src/pages/MyTicketsPage.tsx, src/hooks/useMarkTicketAsUsed.ts |
| Batch / Legacy Flow | Direct EVM writes for features not yet ported to Push universal flow | src/hooks/useBatchListing.ts, src/components/marketplace/BulkListingModal.tsx |
| Docs & Troubleshooting | Historical fixes and configuration notes | docs/push-executor-mapping-fix.md, docs/solana-devnet-universal-purchase-fix.md |
-
Dependencies
@pushchain/ui-kitprovides the wallet provider, UI controls, and low-level helpers.wagmi,viem, andethersare used for contract reads and JSON-RPC access.
-
Environment Variables (in
frontend/ticketchain/.env.localor similar):VITE_TICKET_FACTORY_ADDRESSVITE_TICKET_NFT_ADDRESSVITE_MARKETPLACE_ADDRESSVITE_PUSH_RPC_URL(optional; defaults tohttps://evm.rpc-testnet-donut-node1.push.org/)
-
Network Configuration
PushChainProviderssetsnetwork: PushUI.CONSTANTS.PUSH_NETWORK.TESTNET.- Custom RPCs for Ethereum Sepolia and Solana devnet are registered under
chainConfig.rpcUrls. src/lib/wagmi.tsdefines the Push Chain testnet (chain id111557560) so wagmi reads point at the same network.
-
Bootstrap
cd frontend/ticketchain npm install npm run dev
src/main.tsxwraps the app withPushChainProviders, ensuring the push wallet is available everywhere.PushChainProviders(insrc/providers/PushChainProvider.tsx) configures:- Login surface (email, Google, wallet)
- Modal appearance
- RPC overrides (Sepolia + Solana devnet)
- App metadata (logo, title, description)
PushUniversalAccountButtonrenders inHeader.tsxfor both desktop and mobile. It opens the Push wallet modal and reflects connection status.- Components use
usePushWalletContext()to readconnectionStatus,handleConnectToPushWallet, and the activeuniversalAccount.
This document teaches an engineer how to recreate TicketChain’s Push Chain integration without needing the original repository. It explains the architecture, the smart contracts involved, required tooling, and the exact frontend wiring patterns to deliver cross-chain ticketing.
Push Chain allows a single “universal” wallet session to orchestrate transactions on multiple chains. TicketChain uses it to sell NFT tickets, run a secondary marketplace, and validate entry at the venue. The system consists of three layers:
- Smart Contracts (Push Chain EVM testnet)
TicketFactory: creates events, configures ticket tiers, handles on-chain purchases, and validates tickets.TicketNFT: ERC-721 implementation storing ticket metadata, ownership history, and “used” status.TicketMarketplace: escrow-based secondary market for listing and purchasing tickets.
- Frontend Application (React + Vite + TypeScript)
- Uses
@pushchain/ui-kitfor wallet UX and universal transaction APIs. - Uses
wagmi+viem+ethersfor on-chain reads and utility helpers.
- Uses
- Off-chain services
- IPFS pinning (image + metadata uploads).
- Optional analytics or search services (out of scope for this document).
The remainder of the guide assumes you are rebuilding the integration inside your own React application.
-
Clone or scaffold a React app (Vite + TypeScript is recommended).
-
Install dependencies:
npm install @pushchain/ui-kit wagmi viem ethers @tanstack/react-query react-hot-toast
-
Obtain contract ABIs and addresses:
- Compile or export ABIs for the three contracts (
TicketFactory,TicketNFT,TicketMarketplace). - Deploy to Push Chain testnet or reuse the latest known addresses (replace the placeholders below with yours).
- Store addresses and ABI JSON files so the frontend can consume them.
- Compile or export ABIs for the three contracts (
-
Configure environment variables (e.g., in
.env.local):VITE_TICKET_FACTORY_ADDRESS=0x...VITE_TICKET_NFT_ADDRESS=0x...VITE_MARKETPLACE_ADDRESS=0x...VITE_PUSH_RPC_URL=https://evm.rpc-testnet-donut-node1.push.org/(optional override)
-
Create a Push Chain provider wrapper (see section 4).
-
Wire contract hooks and UI screens following sections 5–7.
| Contract | Network | Key Functions | Notes |
|---|---|---|---|
TicketFactory |
Push Chain testnet (Chain ID 111557560) |
createEvent, addTicketType, purchaseTickets, validateTicket, getEvent, getTicketTypes |
Primary entry point for event organizers and buyers |
TicketNFT |
Same | getUserTicketsWithDetails, ticketDetails, ownerOf, validateTicket (internal) |
Stores ownership and metadata for each ticket NFT |
TicketMarketplace |
Same | listTicket, buyTicket, cancelListing, getActiveListings, batchListTickets |
Secondary market with custody escrow |
Cross-chain payments: All primary and secondary purchases send value in Push Chain’s native token (PC). Push Chain’s universal account bridges from the user’s origin chain when necessary.
Create a dedicated component (e.g., PushChainProviders.tsx) that exposes login options and app branding:
import {
PushUniversalWalletProvider,
PushUI,
type AppMetadata,
type ProviderConfigProps,
} from "@pushchain/ui-kit";
const walletConfig: ProviderConfigProps = {
network: PushUI.CONSTANTS.PUSH_NETWORK.TESTNET,
login: {
email: true,
google: true,
wallet: { enabled: true },
appPreview: true,
},
modal: {
loginLayout: PushUI.CONSTANTS.LOGIN.LAYOUT.SPLIT,
connectedLayout: PushUI.CONSTANTS.CONNECTED.LAYOUT.HOVER,
appPreview: true,
connectedInteraction: PushUI.CONSTANTS.CONNECTED.INTERACTION.BLUR,
},
chainConfig: {
rpcUrls: {
"eip155:11155111": ["https://rpc.sepolia.org"],
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": [
"https://api.devnet.solana.com",
],
},
},
};
const appMetadata: AppMetadata = {
title: "TicketChain",
description: "Universal ticketing powered by Push Chain",
logoUrl: "https://avatars.githubusercontent.com/u/64157541?v=4",
};
export function PushChainProviders({
children,
}: {
children: React.ReactNode;
}) {
return (
<PushUniversalWalletProvider config={walletConfig} app={appMetadata}>
{children}
</PushUniversalWalletProvider>
);
}Wrap your React tree (e.g., in main.tsx) with PushChainProviders, WagmiProvider, and QueryClientProvider.
Add the Push wallet button to your navigation:
import {
PushUniversalAccountButton,
usePushWalletContext,
} from "@pushchain/ui-kit";
function Header() {
const { connectionStatus } = usePushWalletContext();
return (
<header>
<PushUniversalAccountButton />
{connectionStatus !== "connected" && <span>Please connect</span>}
</header>
);
}usePushWalletContext() surfaces:
connectionStatus:"connected","connecting", etc.handleConnectToPushWallet(): programmatic trigger for the modal.universalAccount: object with origin address and namespace information.
Some reads do not require wallet context. Reference the Push RPC with ethers:
import { ethers } from "ethers";
export const pushProvider = new ethers.JsonRpcProvider(
import.meta.env.VITE_PUSH_RPC_URL ??
"https://evm.rpc-testnet-donut-node1.push.org/"
);Use this provider for background refresh tasks or server-side scripts.
-
Connection – When the user authenticates,
usePushChainClient()becomes available and exposespushChainClient.universalhelpers. -
Origin vs. executor addresses – A universal account may originate from another chain (e.g., Sepolia or Solana). Convert it to the Push executor address before querying contracts:
const { PushChain } = usePushChain(); const executor = await PushChain.utils.account.convertOriginToExecutor( universalAccount, { onlyCompute: true, } );
-
Explorer links – Use
pushChainClient.explorer.getTransactionUrl(hash)to deep-link confirmations in UI toasts or receipts.
All write flows use pushChainClient.universal.sendTransaction so the universal wallet can manage cross-chain fees.
const tx = await pushChainClient.universal.sendTransaction({
to: TICKET_FACTORY_ADDRESS,
data: PushChain.utils.helpers.encodeTxData({
abi: TicketFactoryABI,
functionName: "createEvent",
args: [
name,
description,
startTime,
endTime,
venue,
eventImageHash,
totalSupply,
royaltyBps,
ticketTypes,
],
}),
});
await tx.wait();- Upload event and ticket tier images to IPFS and pass the resulting hashes.
- Clear any cached drafts after success.
const tx = await pushChainClient.universal.sendTransaction({
to: TICKET_FACTORY_ADDRESS,
data: PushChain.utils.helpers.encodeTxData({
abi: TicketFactoryABI,
functionName: "purchaseTickets",
args: [eventId, ticketTypeId, quantity],
}),
value: ticketPrice * quantity, // denominated in PC
});
await tx.wait();- Block purchases if
connectionStatus !== "connected". - Warn when the wallet’s origin chain differs from the selected payment chain.
- List: approve the marketplace to transfer the NFT, then call
TicketMarketplace.listTicket(tokenId, price). - Buy: send
valueequal to the listing price when callingbuyTicket. - Cancel: call
cancelListing(listingId); no value required. - Batch list (optional): current implementation uses wagmi’s
writeContractdirectly. Porting tosendTransactionis recommended for full universal support.
const tx = await pushChainClient.universal.sendTransaction({
to: TICKET_FACTORY_ADDRESS,
data: PushChain.utils.helpers.encodeTxData({
abi: TicketFactoryABI,
functionName: "validateTicket",
args: [eventId, tokenId],
}),
});
await tx.wait();Use QR scanning to capture the tokenId and eventId, then verify ownership by reading TicketNFT.ticketDetails and TicketFactory.getEvent before sending the transaction.
- Events listing –
useReadContract+TicketFactory.getEventfor each id. - Ticket types –
TicketFactory.getTicketTypes(eventId); deriveticketTypeIdfrom array index. - User tickets – Resolve executor address, then
TicketNFT.getUserTicketsWithDetails(executorAddress). - Marketplace listings –
TicketMarketplace.getActiveListings(); join with ticket metadata for display. - Caching – After any successful write, invalidate cached data (e.g., clear
localStorageor invokequeryClient.invalidateQueries).
- Local smoke test – start the dev server, connect with both native Push wallet and a bridged wallet (Sepolia or Solana).
- Event creation – create an event, confirm transaction on the Push explorer, and verify ticket tiers render.
- Primary purchase – buy different ticket quantities, inspect toast lifecycle and explorer links.
- My tickets – switch origin chains; ensure executor resolution succeeds and tickets appear.
- Marketplace – list a ticket, buy it from another wallet, cancel a listing, and verify escrow transfers.
- Validation – scan the ticket QR at the venue UI and mark it as used.
- Batch listing – if you keep the wagmi implementation, connect an EVM wallet directly to Push testnet and try listing multiple tickets. Document this limitation for stakeholders.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Wallet button does nothing | Provider wrapper missing | Ensure PushChainProviders wraps the React tree |
| Empty ticket list after purchase | Executor mapping failed | Retry convertOriginToExecutor or reconnect wallet |
IncorrectPayment revert |
Price mismatch or stale cache | Refresh ticket data and resend transaction with correct PC value |
executePayload failure |
Origin chain mismatch or insufficient gas | Switch wallet to the chain selected in the UI, confirm balance |
| Solana payments fail | Wallet not on devnet or low SOL | Switch to devnet and fund via faucet |
| Batch listing rejected | Using universal account for wagmi write | Use an EVM wallet on Push testnet or refactor to sendTransaction |
- Log transaction payloads before sending (ticket type IDs, payment values).
- Use the returned transaction hash with
pushChainClient.explorer.getTransactionUrl(hash)to inspect execution. - Leverage the read-only
pushProviderfor quick console scripts without requiring wallet connection.
- Extract executor resolution into a reusable hook (e.g.,
usePushExecutorAddress) to share logic across views. - Replace the wagmi-only batch listing path with universal transactions for cross-chain parity.
- Add automated integration tests that simulate external wallets (Sepolia/Solana) to guard against regressions.
- Display a global banner when the wallet’s origin chain diverges from the required payment chain.
Last updated: 2025-10-19