How Pepesdog Tip Works
A technical overview of the architecture, self-custody model, and smart contracts behind multi-chain Twitter tipping.
Overview
Pepesdog Tip is a multi-chain payment platform that lets anyone send crypto to anyone on X. Pick a token, search for a recipient, and send — no wallet setup, no gas fees. Tips execute on Base (ERC-20), Arbitrum One (ERC-20), or Solana (SPL tokens), with all gas fees paid by the platform.
Token listing is permissionless. Anyone can list a token for tipping by staking 2,500B ZEUS on Ethereum mainnet. There is no approval process, no lock period, and no slashing. Unstake anytime to delist.
Every user gets a deterministic smart contract wallet derived from their Twitter handle. Only the wallet owner can withdraw funds. The platform can relay tips between wallets but can never extract tokens to an external address. This is enforced at the smart contract level, not by policy.
Architecture
ETHEREUM BASE L2 ARBITRUM ONE SOLANA
┌────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐
│ StakingRegistry│ │ PingTipManager │ │ PingTipManager │ │ ping_vault │
│ V3 (generic │ │ PingWalletFact. │ │ PingWalletFact. │ │ (Anchor) │
│ uint8 chain) │ │ PingWallet │ │ PingWallet │ │ PDA wallets │
│ ZEUS stake │ │ (CREATE2) │ │ (CREATE2) │ │ │
└────────┬───────┘ └────────┬────────┘ └────────┬────────┘ └────────┬───────┘
│ │ │ │
└────────────────────┴──────────┬──────────┴─────────────────────┘
│
┌───────▼────────┐
│ Backend │
│ │
│ Tip API │
│ Chain router │
│ Relayers (KMS) │
│ Price service │
│ │
└───────┬────────┘
│
┌───────▼────────┐
│ Frontend │
│ tips.pepes.dog │
└────────────────┘Ethereum hosts the StakingRegistryV3 — the on-chain registry where tokens are listed by staking ZEUS. The contract accepts any uint8 chain identifier, so new chains can be added without redeploying the registry. No tips execute on Ethereum.
Base L2 and Arbitrum One handle ERC-20 tip execution. Each user has a deterministic PingWallet smart contract per chain, deployed via CREATE2. The PingTipManager contract orchestrates tip transfers between wallets, called by an authorized relayer. The same source code is deployed on both networks.
Solana handles SPL token tips. The ping_vault Anchor program manages PDA-based per-user vaults with an equivalent authority model.
The Next.js backend receives tip requests from the webapp, routes transactions to the correct chain, and returns a shareable transaction link.
Self-Custody Model
This is the most important thing to understand about Pepesdog Tip. Your tokens are held in a smart contract wallet that only you can withdraw from. This is not a policy promise — it is enforced by immutable code on-chain.
How it works
When someone tips you, the backend creates a deterministic wallet for your Twitter handle if one does not already exist. On Base and Arbitrum, this is a PingWallet contract deployed via CREATE2. On Solana, it is a Program Derived Address (PDA). The wallet address is computed from your handle — the same handle always produces the same address.
Before you claim your wallet, the owner field is address(0). This means no one can withdraw — not even the platform. The withdraw function has an onlyOwner modifier that reverts if the caller is not the owner:
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner();
_;
}
function withdraw(
address token,
uint256 amount,
address to
) external onlyOwner nonReentrant {
if (to == address(0)) revert ZeroAddress();
if (amount == 0) revert ZeroAmount();
IERC20(token).safeTransfer(to, amount);
emit Withdrawn(token, to, amount);
}When you “claim” your wallet by connecting MetaMask or Phantom on the dashboard, the backend calls claimWallet(handle, yourAddress) on the factory. This sets your EOA as the owner. From that point, only your wallet can call withdraw().
The delegate pattern
The platform needs to move tokens between tip wallets when someone sends a tip. This is handled by the delegate pattern. The PingTipManager contract is set as a delegate on each wallet during creation. Delegates can call executeTokenTransfer() to move tokens from one tip wallet to another — but they cannot call withdraw():
// Delegates can transfer (for tips) — NOT withdraw
function executeTokenTransfer(
address token,
address to,
uint256 amount
) external onlyOwnerOrDelegate nonReentrant { ... }
// Only the owner can withdraw — delegates cannot
function withdraw(
address token,
uint256 amount,
address to
) external onlyOwner nonReentrant { ... }In summary: the relayer can shuffle tokens between tip wallets as instructed by the webapp, but it has no path to withdraw tokens to any external address. The withdraw function is exclusively gated by onlyOwner.
MaxTipAmount protection
As an additional safeguard, each token has a maximum tip amount set by the lister when staking. Users can lower this limit for their own wallet. If a tip exceeds the maximum, the transaction reverts:
uint256 maxTip = maxTipAmounts[token];
if (maxTip > 0 && amount > maxTip) {
revert ExceedsMaxTipAmount(amount, maxAllowed);
}Smart Contracts — Base & Arbitrum
The same PingWallet, PingWalletFactory, and PingTipManager source code is deployed on both Base (chain ID 8453) and Arbitrum One (chain ID 42161). Because the deployer EOA used the same nonce sequence on both chains, the contract addresses are identical — but they are completely independent contracts with chain-local state. Users claim each chain separately.
Per-user self-custody wallet. One instance per Twitter handle, deployed deterministically via CREATE2 from the factory. Holds ERC-20 token balances for tips. Same source deployed on Base and Arbitrum.
Deployed per-user via CREATE2 (no fixed address)
Key Functions
withdraw(token, amount, to) — onlyOwner
withdrawETH(amount, to) — onlyOwner
executeTokenTransfer(token, to, amt) — onlyOwnerOrDelegate
setMaxTipAmount(token, amount) — onlyOwner
addDelegate(delegate) — onlyOwner
removeDelegate(delegate) — onlyOwnerSecurity Properties
- owner is address(0) until claimed — no one can withdraw before claim
- withdraw() gated by onlyOwner — delegates cannot withdraw
- ReentrancyGuard on all state-changing functions
- SafeERC20 for all token transfers
- initializeDelegate can only be called once (_delegateInitialized flag)
- FACTORY address is immutable (set in constructor)
- MaxTipAmount protection on executeTokenTransfer
CREATE2 factory that deploys PingWallet instances on Base. The tipManager address is immutable — set once in the constructor and cannot be changed. Computes deterministic wallet addresses from Twitter handles without requiring any RPC call.
0xb07e2c122B247CBaE6911a3954c2738DaCbb1c0B
Key Functions
computeAddress(handle) → address — view, deterministic
createWallet(handle) → address — onlyAuthorized
getOrCreate(handle) → address — onlyAuthorized
claimWallet(handle, newOwner) — onlyAuthorized
isDeployed(handle) → bool — viewSecurity Properties
- tipManager is immutable — cannot be changed after deployment
- Only authorizedBackends can create or claim wallets
- initializeDelegate called exactly once per wallet during creation
- CREATE2 guarantees deterministic addresses — same handle = same address
CREATE2 factory that deploys PingWallet instances on Arbitrum One. Identical source code to the Base factory. Authorized to create and claim wallets by the backend relayer.
0xb07e2c122B247CBaE6911a3954c2738DaCbb1c0B
Key Functions
computeAddress(handle) → address — view, deterministic
createWallet(handle) → address — onlyAuthorized
getOrCreate(handle) → address — onlyAuthorized
claimWallet(handle, newOwner) — onlyAuthorized
isDeployed(handle) → bool — viewSecurity Properties
- tipManager is immutable — cannot be changed after deployment
- Only authorizedBackends can create or claim wallets
- initializeDelegate called exactly once per wallet during creation
- CREATE2 guarantees deterministic addresses — same handle = same address
Relayer-callable tip executor on Base. Authorized relayers call executeTip() to transfer tokens from one PingWallet to another. The TipManager itself holds no funds and has no withdrawal capability.
0x019653D09D14eFbD6Bc75382235B7c8Df4F88554
Key Functions
executeTip(token, fromWallet, toWallet, amount) — onlyRelayer
authorizeRelayer(relayer) — onlyOwner
revokeRelayer(relayer) — onlyOwnerSecurity Properties
- Only authorizedRelayers can call executeTip
- ReentrancyGuard on executeTip
- Contract holds no funds — purely an orchestration layer
- Cannot call withdraw() on PingWallet — only executeTokenTransfer()
Relayer-callable tip executor on Arbitrum One. Identical source code to the Base TipManager. The same AWS KMS key signs tip transactions for both chains.
0x019653D09D14eFbD6Bc75382235B7c8Df4F88554
Key Functions
executeTip(token, fromWallet, toWallet, amount) — onlyRelayer
authorizeRelayer(relayer) — onlyOwner
revokeRelayer(relayer) — onlyOwnerSecurity Properties
- Only authorizedRelayers can call executeTip
- ReentrancyGuard on executeTip
- Contract holds no funds — purely an orchestration layer
- Same KMS-backed relayer EOA as Base — single hardware-secured key signs across chains
Smart Contracts — Ethereum
On-chain permissionless registry for token listing. Anyone can list a token by staking ZEUS (currently 2,500B — adjustable by the owner for future listings). No admin can withdraw staked tokens. The chain field is a generic uint8 so new chains (Base, Arbitrum, Solana, or future networks) can be listed without redeploying this contract. When unstaking, the contract always returns the exact amount originally staked — regardless of any subsequent changes to the requirement.
0xd48795ac8d448928d00474Ae06A2564e0363542b
Key Functions
stake(tokenAddress, chain, hashtag, coingeckoId, maxTipAmount)
unstake(tokenAddress, chain) — onlyStaker
isListed(tokenAddress, chain) → bool — view
getListingByHashtag(hashtag) → Listing — view
setStakeAmount(newAmount) — onlyOwner
transferOwnership(newOwner) — onlyOwnerSecurity Properties
- No admin withdrawal function exists — owner cannot access staked tokens
- unstake() returns the exact stakedAmount to the original staker, not the current stakeAmount
- setStakeAmount only affects new listings — existing stakes are not affected
- Generic uint8 chain field — new chains added off-chain, no redeploy required
- ReentrancyGuard on stake/unstake
- SafeERC20 for all token transfers
- ZEUS_TOKEN address is immutable
- Hashtags are case-insensitive and globally unique
Solana Program
Anchor program managing PDA-based per-user vaults on Solana. Each Twitter handle maps to a deterministic PDA. The authority model mirrors Base: the backend can execute tips, but only the wallet owner can withdraw. Supports both standard SPL Token and Token-2022 mints via token_interface.
8R6yamgQEWRZCD6JDPLytJBQNLRBweYNiLAoAizmwuWM
Key Functions
initialize_config() — one-time setup
initialize_user(handle) — create user PDA
claim_wallet(handle, owner) — set owner pubkey
execute_tip(token, from, to, amount) — authority only
withdraw(token, amount) — owner signature required
set_max_tip_amount(token, amount) — owner onlySecurity Properties
- PDA derivation: seeds ["user", handle_bytes] — deterministic, same handle = same address
- withdraw requires owner signature — backend authority cannot withdraw
- MaxTipConfig PDA protects sender with per-token limits
- Supports SPL Token and Token-2022 via anchor_spl::token_interface
- Uses transfer_checked for all token transfers (required by Token-2022)
- security.txt embedded in the program binary
- Verified build via solana-verify (deterministic Docker build)
Solana Verified Build: verify.osec.io/status/8R6yamgQEWRZCD6JDPLytJBQNLRBweYNiLAoAizmwuWM — program hash 7b6fa7e7...ee428b2 matches the deterministic build from zeus-army/tip-verify.
Token Listing & Staking
Token listing is fully permissionless. To list a token for tipping:
- Approve the StakingRegistryV3 contract to spend 2,500B ZEUS from your wallet.
- Call
stake()with the token address, chain (Base, Arbitrum, or Solana), a unique hashtag (1-32 characters), a CoinGecko ID (for price/logo data), and a maximum tip amount. - The registry transfers 2,500B ZEUS from your wallet to the contract. Your token is immediately listed.
To delist:
- Call
unstake()with the token address and chain. - The registry returns the exact amount you staked (even if the required stake amount has since changed) and immediately delists the token.
There is no lock period, no slashing, and no approval process. The owner of the registry contract can change the stake amount for future listings, but cannot affect existing stakes or withdraw anyone's ZEUS.
How Tipping Works
Sending a tip
Log in with your Twitter account on tips.pepes.dog, pick a token from your balance, search for anyone on X by handle, enter an amount, and click send. The backend validates the request, checks your balance, and executes the on-chain transfer. The whole flow takes a few seconds.
Multiple recipients
Tips can also be sent to multiple recipients at once via drops (Twitter Drops and Telegram Drops), which distribute tokens to a list of users in a single operation.
Receiving a tip
Recipients do not need an account to receive tokens. A deterministic wallet is created for their Twitter handle automatically. They can log in later to view their balance, claim the wallet, and withdraw to any external address.
Privacy
The platform processes only public Twitter profile information (handle and user ID) needed to route tips. It does not access DMs, followers lists, or any private user data.
Gas Sponsorship
All gas fees for tip transactions are paid by the platform. Users never need to hold ETH or SOL to send or receive tips.
The EVM relayer key is managed with AWS KMS — a single secp256k1 key stored in a Hardware Security Module (HSM). The same key signs transactions for both Base and Arbitrum One: the chain identifier lives in the EIP-1559 envelope, so one KMS key yields a valid signature for any EVM chain and the relayer has the same EOA address everywhere. Private keys never exist in application memory.
For Solana, a separate Ed25519 keypair is stored in Vercel secret environment variables (KMS does not support pure Ed25519).
Withdrawals
On Base and Arbitrum, the user signs the withdraw transaction directly with their own wallet — withdrawals are not gasless. On Solana, the user signs the withdrawal transaction directly and pays the minimal SOL fee (less than $0.001).
Security Considerations
- All EVM contracts are verified on Etherscan, Basescan, and Arbiscan — source code matches deployed bytecode
- Solana program verified via solana-verify deterministic Docker build, with security.txt embedded in the binary
- ReentrancyGuard (OpenZeppelin) on all state-changing functions across all EVM contracts
- SafeERC20 (OpenZeppelin) for all token transfers — prevents silent transfer failures
- No admin function exists to drain funds from any contract
- The delegate pattern allows the relayer to move tokens between tip wallets, but not to withdraw to external addresses — this is enforced by separate onlyOwner and onlyOwnerOrDelegate modifiers
- MaxTipAmount protection: the token lister sets a default maximum, and individual users can lower it for their own wallet
- The tipManager address in PingWalletFactory is immutable — it cannot be changed after deployment
- initializeDelegate has a one-time guard — the TipManager delegate cannot be re-assigned
- CREATE2 and PDA derivation are deterministic — wallet addresses cannot be manipulated
- AWS KMS HSM for the EVM relayer key (Base + Arbitrum share the same key) — private keys never exist in application memory
- StakingRegistryV3 returns the exact staked amount on unstake — even if the required amount has changed
- StakingRegistryV3 uses a generic uint8 chain field — new chains are added off-chain without needing contract upgrades
Open Source & Verification
All smart contract source code is publicly available and independently verifiable:
- Contract source code
- StakingRegistryV3 on Etherscan
- PingTipManager on Basescan
- PingWalletFactory on Basescan
- PingTipManager on Arbiscan
- PingWalletFactory on Arbiscan
- ping_vault on Solscan (verified)
- Solana Verified Build (solana-verify)
Last updated April 2026. If you have security concerns, please reach out on Telegram.