-
Notifications
You must be signed in to change notification settings - Fork 76
Description
Overview
Replace flat XLM tipping with a gift system — viewers send named gifts priced in USD and paid in USDC on Stellar. Fixed USD pricing removes the confusion of XLM price volatility. This is the TikTok/Bigo Live engagement mechanic: a great moment gets a Dragon, the chat goes wild, new viewers join.
Gift tiers (USD pricing, paid in USDC)
| Gift | Emoji | USD / USDC | Animation |
|---|---|---|---|
| Flower | 🌸 | $1 | Small float in chat |
| Candy | 🍬 | $5 | Confetti burst |
| Crown | 👑 | $25 | Gold banner |
| Lion | 🦁 | $100 | Full-width roar overlay |
| Dragon | 🐉 | $500 | Full-screen dragon fly-across |
Gift values stored in DB (not hardcoded) so they can be adjusted without a deploy.
Why USDC not XLM
- Stable: $1 gift is always $1 — no "is 500 XLM a lot right now?" confusion
- Infrastructure already in place: USDC runs natively on Stellar (issued by Circle)
- Transak support: Transak can on-ramp fiat directly to USDC on Stellar
- Creator UX: Creators receive stable dollars, not volatile XLM
USDC trustline (one-time setup per wallet)
Before a wallet can send or receive USDC on Stellar it needs a trustline. This is a one-time on-chain operation costing 0.5 XLM reserve.
// lib/stellar/usdc.ts
export const USDC_ISSUER_MAINNET =
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN";
export const USDC_ISSUER_TESTNET =
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
export function getUsdcAsset(network: "mainnet" | "testnet") {
return new Asset(
"USDC",
network === "mainnet" ? USDC_ISSUER_MAINNET : USDC_ISSUER_TESTNET
);
}
export async function hasUsdcTrustline(
publicKey: string,
network: "mainnet" | "testnet"
): Promise<boolean> {
const server = getStellarServer(network);
const account = await server.loadAccount(publicKey);
const issuer =
network === "mainnet" ? USDC_ISSUER_MAINNET : USDC_ISSUER_TESTNET;
return account.balances.some(
b =>
b.asset_type === "credit_alphanum4" &&
b.asset_code === "USDC" &&
b.asset_issuer === issuer
);
}
export async function buildUsdcTrustlineTransaction(
publicKey: string,
network: "mainnet" | "testnet"
) {
const server = getStellarServer(network);
const account = await server.loadAccount(publicKey);
const usdcAsset = getUsdcAsset(network);
return new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase:
network === "mainnet" ? Networks.PUBLIC : Networks.TESTNET,
})
.addOperation(Operation.changeTrust({ asset: usdcAsset }))
.setTimeout(30)
.build();
}UX flow: when the gift picker is opened for the first time, check hasUsdcTrustline(). If false, show a one-time "Enable USDC Gifts" dialog explaining what it does and costing 0.5 XLM reserve. After signing, the trustline is created and the gift picker opens normally. This check is skipped on all subsequent opens.
USDC payment (sending a gift)
Extend lib/stellar/payments.ts with buildGiftTransaction:
export async function buildGiftTransaction({
sourcePublicKey,
destinationPublicKey,
usdcAmount, // e.g. "25.00" for a Crown
network,
}: GiftTransactionParams) {
const server = getStellarServer(network);
const account = await server.loadAccount(sourcePublicKey);
const usdcAsset = getUsdcAsset(network);
return new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase:
network === "mainnet" ? Networks.PUBLIC : Networks.TESTNET,
})
.addOperation(
Operation.payment({
destination: destinationPublicKey,
asset: usdcAsset,
amount: usdcAmount,
})
)
.addMemo(Memo.text("StreamFi Gift"))
.setTimeout(30)
.build();
}DB schema
CREATE TABLE gifts (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
emoji TEXT NOT NULL,
usd_value NUMERIC(10,2) NOT NULL,
sort_order INT NOT NULL,
animation TEXT NOT NULL,
active BOOLEAN DEFAULT true
);
INSERT INTO gifts (name, emoji, usd_value, sort_order, animation) VALUES
('Flower', '🌸', 1.00, 1, 'float'),
('Candy', '🍬', 5.00, 2, 'confetti'),
('Crown', '👑', 25.00, 3, 'banner'),
('Lion', '🦁', 100.00, 4, 'roar'),
('Dragon', '🐉', 500.00, 5, 'dragon');
-- Gift messages stored in chat
-- chat message type: 'gift'
-- metadata JSONB includes: { gift_name, gift_emoji, usd_value, tx_hash }API routes
GET /api/gifts— returns active gift list (cached, rarely changes)- Gift payments use the existing Stellar payment infrastructure (
buildGiftTransaction) - Gift messages posted to
/api/streams/chatwithtype: 'gift'
Frontend components
GiftPicker (components/stream/GiftPicker.tsx)
- Opens from a 🎁 button in the chat input area
- On first open: checks USDC trustline → shows "Enable USDC" dialog if needed
- Grid of gift cards: emoji, name, price in $ (e.g. "$25")
- Tap → confirmation sheet: "Send 👑 Crown to @alice for $25 USDC?"
- On confirm:
buildGiftTransaction→ sign → submit - On success: posts gift chat message, plays animation
- On insufficient USDC balance: shows
AddFundsButtonwithcryptoCurrencyCode: "USDC"override
GiftMessage (components/stream/GiftMessage.tsx)
- Renders in chat for
type === 'gift'messages - Gradient background per tier (flower = pink, dragon = purple/gold)
- Animated entrance (framer-motion)
- Shows: avatar, "@username sent a 🐉 Dragon — $500"
Stream overlay animations
- Lion and Dragon: full-width/full-screen framer-motion animation on video area
- Auto-dismisses after 4 seconds
- Multiple gifts queue sequentially
Acceptance criteria
-
giftstable seeded with 5 tiers -
GET /api/giftsreturns active gifts -
hasUsdcTrustline()check implemented - "Enable USDC Gifts" one-time trustline setup flow works
- Gift picker UI accessible from chat
- USDC payment sends correct amount to streamer wallet
- Gift appears as special chat message for all viewers
- Dragon/Lion animations play on stream overlay
-
AddFundsButtonwith USDC config shown on insufficient balance - Streamers receive USDC (stable) not XLM