PayLink is an open-source payment link system built on Starknet. It lets anyone send and receive USDC via a shareable link — no wallets, no seed phrases, no crypto knowledge required.
Live Demo: paylink001.vercel.app
Network: Starknet Sepolia Testnet
Contract: 0x0585589...
SDK: npmjs.com/package/paylink-sdk
A creator visits PayLink, logs in with Google, and gets a link like
paylink.app/mekjah. They share that link anywhere. Anyone who visits
it can send USDC in under 60 seconds — no wallet setup, no crypto jargon.
Creator flow:
- Sign in with Google → wallet created silently via Privy + StarkZap
- Claim a username → registered on-chain via Cairo contract
- Share
paylink001.vercel.app/usernameanywhere - Dashboard shows live balance and transaction history
Payer flow:
- Visit a PayLink URL
- Enter amount → sign in with Google (wallet created automatically)
- Fund wallet with USDC → confirm payment
- Done — never saw a seed phrase or gas fee
A freelancer wants to accept crypto payment from a client. Here is what happens:
- Client downloads MetaMask or Braavos wallet browser extension
- Client creates an account — writes down a 12-word seed phrase
- Client buys USDC on a centralised exchange (Coinbase, Binance)
- Client waits 1-3 days for identity verification to clear
- Client bridges funds to Starknet — pays bridge fees, waits 10 minutes
- Client acquires STRK separately to pay gas fees
- Client copies the freelancer's 66-character hex wallet address
- Client submits transaction — signs a hex string they don't understand
- Transaction fails because gas estimate was wrong
- Client gives up
Result: Payment never sent. Freelancer loses the client.
- Freelancer shares
paylink.app/mekjah - Client visits the link
- Client clicks Pay, signs in with Google
- Client funds their wallet with a card (MoonPay)
- Client clicks Confirm
- Done
Result: Payment sent in under 60 seconds. No seed phrases. No bridge. No gas management. No wallet downloads. The client never knew they were using a blockchain.
| Step | Web2 experience | What actually happened |
|---|---|---|
| Sign in with Google | Familiar OAuth flow | Privy created an embedded wallet |
| Wallet created automatically | User saw nothing | StarkZap derived a Starknet account |
| No gas fee prompt | User paid nothing extra | Server prefunded wallet with STRK |
| Payment confirmed | Success screen | Real USDC transfer on Starknet Sepolia |
| Link like paylink.app/mekjah | Simple URL | Username resolved from Cairo contract on-chain |
- Google login via Privy — no wallet download needed
- Silent wallet creation — StarkZap generates and manages wallets server-side
- On-chain username registry — Cairo contract maps usernames to addresses trustlessly
- Auto gas funding — server prefunds new wallets with STRK so users never buy gas
- Transaction history — persisted server-side, scoped per wallet address
- MoonPay integration — credit card onramp (sandbox, pending production approval)
- Gasless payments — AVNU paymaster integration (pending Propulsion Program approval)
- Fully open source — drop-in SDK available on npm
| Feature | Status |
|---|---|
| Google login + wallet creation | ✅ Working |
| USDC transfer on Sepolia | ✅ Working |
| On-chain username registry | ✅ Working |
| Auto STRK gas prefunding | ✅ Working |
| Transaction history (sender + receiver) | ✅ Working |
| Wallet-to-wallet withdrawal | ✅ Working |
| MoonPay credit card onramp | 🔄 Sandbox — pending production approval |
| AVNU gasless payments | 🔄 Pending Propulsion Program approval |
| Layer | Technology |
|---|---|
| Frontend | React + Vite + TypeScript |
| Styling | Tailwind CSS |
| State | Zustand with persistence |
| Routing | React Router DOM |
| Auth | Privy (Google/email login) |
| Wallet | StarkZap SDK |
| Chain | Starknet Sepolia |
| Smart Contract | Cairo 2.0 |
| Backend | Node.js + Express |
paylink/ ├── apps/ │ ├── web/ # React frontend (deployed to Vercel) │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── paylink/ │ │ │ │ │ ├── PaymentCard.tsx # Core payment UI + state machine │ │ │ │ │ ├── ProfileHeader.tsx # Payment page header + avatar │ │ │ │ │ ├── DashboardCard.tsx # Live USDC balance display │ │ │ │ │ ├── TransactionList.tsx # Sent + received history │ │ │ │ │ ├── CreateUsername.tsx # On-chain username registration │ │ │ │ │ ├── WithdrawModal.tsx # Bank withdrawal UI │ │ │ │ │ └── WithdrawToAddressModal.tsx # Wallet-to-wallet transfer │ │ │ │ └── ui/ # Shared design system components │ │ │ │ ├── Button.tsx │ │ │ │ ├── Card.tsx │ │ │ │ ├── Modal.tsx │ │ │ │ ├── Toast.tsx │ │ │ │ ├── Navbar.tsx │ │ │ │ ├── Footer.tsx │ │ │ │ ├── AuthModal.tsx │ │ │ │ └── Section.tsx │ │ │ ├── hooks/ │ │ │ │ ├── useAuth.ts # Privy + StarkZap wallet management │ │ │ │ ├── usePaymentFlow.ts # Payment state machine │ │ │ │ ├── useStore.ts # Zustand stores (auth, payment, UI) │ │ │ │ └── useUnifiedTransactions.ts # Merge local + server transactions │ │ │ ├── lib/ │ │ │ │ ├── address.ts # Address normalization utilities │ │ │ │ ├── registry.ts # Cairo contract interaction │ │ │ │ ├── transactions.ts # Server API helpers │ │ │ │ ├── withdraw.ts # On-chain withdrawal logic │ │ │ │ └── moonpay.ts # MoonPay onramp integration │ │ │ ├── pages/ │ │ │ │ ├── LandingPage.tsx # Homepage + product explainer │ │ │ │ ├── LoginPage.tsx # Creator login/signup │ │ │ │ ├── DashboardPage.tsx # Creator dashboard (protected) │ │ │ │ ├── PaymentPage.tsx # Public payment page /:username │ │ │ │ ├── Terms.tsx │ │ │ │ ├── Privacy.tsx │ │ │ │ └── Support.tsx │ │ │ ├── types/ │ │ │ │ └── payment.ts # PaymentStep, Transaction types │ │ │ ├── abis/ │ │ │ │ └── contractAbi.ts # UsernameRegistry ABI │ │ │ └── App.tsx # Router + Privy provider │ │ ├── public/ │ │ ├── index.html │ │ ├── vite.config.ts │ │ ├── tsconfig.json │ │ ├── tailwind.config.ts │ │ ├── postcss.config.js │ │ ├── package.json │ │ └── vercel.json # SPA routing config │ │ │ └── server/ # Express backend (deployed to Railway) │ ├── index.js # Main server — wallet, transactions, funding │ ├── transactions.json # Persisted transaction store (gitignored) │ ├── registry.json # Username → address cache (gitignored) │ └── package.json │ ├── packages/ │ └── paylink-sdk/ # Published to npm as paylink-sdk │ ├── src/ │ │ ├── components/ │ │ │ ├── PaymentCard.tsx │ │ │ ├── ProfileHeader.tsx │ │ │ └── PayLinkProvider.tsx │ │ ├── hooks/ │ │ │ ├── useAuth.ts │ │ │ └── usePaymentFlow.ts │ │ ├── lib/ │ │ │ ├── address.ts │ │ │ ├── registry.ts │ │ │ └── moonpay.ts │ │ └── index.ts # Public exports │ ├── dist/ # Built output (gitignored) │ ├── package.json │ └── tsconfig.json │ ├── contracts/ # Cairo smart contracts │ └── UsernameRegistry/ │ └── src/ │ └── lib.cairo # register_name, resolve_name, reverse_resolve │ ├── pnpm-workspace.yaml ├── package.json # Monorepo root ├── pnpm-lock.yaml ├── .env # Gitignored ├── .env.example ├── .gitignore ├── railway.json └── README.md
- Node.js 18+
- pnpm (recommended) or npm
npm install -g pnpmgit clone https://github.com/MJ-RWA/paylink.git
cd paylink
pnpm installCopy .env.example to .env:
cp .env.example .envFill in your values:
# Privy
VITE_PRIVY_APP_ID=your_privy_app_id
PRIVY_SECRET_KEY=your_privy_secret
# Network
VITE_NETWORK=sepolia
VITE_SERVER_URL=http://localhost:3001
# Deployer wallet (funds new user wallets with STRK gas)
DEPLOYER_ADDRESS=0x_your_deployer_address
DEPLOYER_PRIVATE_KEY=0x_your_deployer_private_key
# Alchemy RPC (free at alchemy.com)
ALCHEMY_API_KEY=your_alchemy_key
VITE_ALCHEMY_API_KEY=your_alchemy_key
# Registry contract (already deployed — do not change)
VITE_REGISTRY_CONTRACT=0x0585589db8cdfee93349d5f7cabf7db8ce3d557c93b1c91cb201e0120672b822
# Optional — pending approvals
VITE_MOONPAY_API_KEY=pk_test_your_key
VITE_AVNU_API_KEY=your_avnu_keyStart both the Vite frontend and Express backend together:
pnpm dev:allOr separately:
# Terminal 1 — frontend
pnpm dev
# Terminal 2 — backend
pnpm serverFrontend runs at http://localhost:5173
Backend runs at http://localhost:3001
pnpm buildPayLink uses a Cairo 2.0 smart contract on Starknet Sepolia to map usernames to wallet addresses trustlessly.
Contract: 0x0585589db8cdfee93349d5f7cabf7db8ce3d557c93b1c91cb201e0120672b822
Explorer: View on Starkscan
When you visit paylink.app/mekjah, the app calls resolve_name("mekjah") on
this contract. The wallet address comes directly from Starknet — not from a
database. Anyone can verify the mapping independently.
fn register_name(name: felt252, address: ContractAddress)
fn resolve_name(name: felt252) -> ContractAddress
fn reverse_resolve(address: ContractAddress) -> felt252Drop PayLink's payment components into any React app to add Starknet payments in minutes.
# npm
npm install paylink-sdk
# pnpm
pnpm add paylink-sdk
# yarn
yarn add paylink-sdkThe SDK requires these packages in your project:
pnpm add react react-dom starkzap @privy-io/react-auth starknetWrap your app with PayLinkProvider and use the components:
import { PayLinkProvider, PaymentCard, ProfileHeader } from 'paylink-sdk';
function App() {
return (
<PayLinkProvider privyAppId="your_privy_app_id">
<YourApp />
</PayLinkProvider>
);
}
// On a payment page
function PayPage() {
return (
<div>
<ProfileHeader username="mekjah" />
<PaymentCard recipientUsername="mekjah" />
</div>
);
}// Components
import { PaymentCard, ProfileHeader, PayLinkProvider } from 'paylink-sdk';
// Hooks
import { usePaymentFlow, useAuth } from 'paylink-sdk';
// Utilities
import { normalizeAddress, addressesMatch, resolveUsername, openMoonPay } from 'paylink-sdk';
// Types
import type { PaymentStep, Transaction } from 'paylink-sdk';VITE_PRIVY_APP_ID=your_privy_app_id
VITE_NETWORK=sepolia
VITE_SERVER_URL=https://your-backend.railway.app
VITE_ALCHEMY_API_KEY=your_key
VITE_REGISTRY_CONTRACT=0x0585589db8cdfee93349d5f7cabf7db8ce3d557c93b1c91cb201e0120672b822PRs are welcome. Please open an issue first for significant changes.
- Fork the repo
- Create a feature branch:
git checkout -b feat/my-feature - Commit your changes:
git commit -m 'feat: add my feature' - Push to the branch:
git push origin feat/my-feature - Open a pull request
MIT