Skip to content

feat: live stream gift system (flower, lion, dragon) priced in usd/usdc #367

@davedumto

Description

@davedumto

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/chat with type: '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 AddFundsButton with cryptoCurrencyCode: "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

  • gifts table seeded with 5 tiers
  • GET /api/gifts returns 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
  • AddFundsButton with USDC config shown on insufficient balance
  • Streamers receive USDC (stable) not XLM

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions