Docs

Giftfolio lets you gift real assets, a graded card (Pokémon, basketball, baseball or football) or a tokenized stock, as a shareable link. Real ownership is held in an on-chain escrow and released to whoever opens the link.

Overview

Every gift follows the same shape: a real asset is locked in a single-purpose escrow contract, and a link is created carrying a one-time secret. Whoever opens the link and reveals that secret can release the asset to their own wallet. The sender can refund an unclaimed gift at any time. Prices, card images, and pack odds are pulled live from the market, nothing is faked.

There are two ways to gift:

Mystery packs

A pack is a sealed gift that reveals one real graded card when opened. It works buy-on-demand: you don't need any inventory or to own a card first.

  1. You pick a pack tier and pay its price in USDC on Polygon.
  2. A backend service verifies the payment, then buys a real Courtyard graded card from the open market (OpenSea) inside that tier's value band.
  3. The card is locked in escrow and sealed into your claim link.
  4. The recipient opens the link, tears the pack, and claims the real card NFT, gasless.

You always receive exactly one real card. If no card can be sourced in the band, or a purchase fails, the order is automatically refunded. The card is priced in USDC.e (the bridged USDC that Courtyard listings use); if you pay in native USDC the backend swaps it for you, so you just hold ordinary USDC. See pack odds for each tier's real value band, pool size, and per-card chances.

Direct gifts

Gift a specific asset you already hold on-chain:

Claiming (gasless)

The recipient opens the link and presses Claim. They connect a wallet to receive the asset, and the real asset is released from escrow straight to it, with an explorer link as the receipt.

For pack cards, claiming is gasless: the treasury submits the claim on the recipient's behalf, so they need no gas at all, just a wallet address to receive the card. A "claim it yourself on-chain" option is always available as well.

Chains & contracts

All contracts are minimal, single-purpose, and verified on the explorer.

ContractChainAddress
PackVendorPolygon (137)0xee57E1B9B87Ca4318E046FAE2C45923f61d8D199
CardDropEscrowPolygon (137)0xb77116D990FA13B19A1E826c664983439dF51e8a
StockDropEscrowRobinhood Chain (4663)0xb77116D990FA13B19A1E826c664983439dF51e8a

Cards are ERC-721s from the Courtyard collection (0x251BE3A17Af4892035C37ebf5890F4a4D889dcAD) on Polygon. Stocks are ERC-20s on Robinhood Chain mainnet. Each escrow moves an asset only two ways: claimed by the holder of the link secret, or refunded to the sender.

Pack API

The buy-on-demand backend is a small service (base URL giftfolio-packs-production.up.railway.app). Payments are idempotent by transaction hash, one payment can never be fulfilled twice.

GET  /api/pack/config
     -> { packs: { grab: { price }, ... }, treasury, usdc, chainId, gasless }

POST /api/pack/fulfill
     { packId, txHash, buyer, to?, note? }
     -> { ok, claimUrl, tokenId, card }        # after the buy succeeds

GET  /api/pack/status/:txHash
     -> { status, claimUrl, card }

POST /api/claim                                # gasless claim relayer
     { kind: "card"|"pack", id, secret, recipient }
     -> { ok, tx, recipient }

GET  /health

Odds & fairness

Within a pack, every card in the pool has an equal chance, a pool of N cards means each card is a 1-in-N draw. The pools, value ranges, and odds shown on the pack odds page are computed live from the same catalog the packs draw from. No hidden numbers, no pay-to-lose: if a pack can't be filled you are refunded.

What's live

Security

The contracts are unaudited. Get a professional audit before scaling real value. See SECURITY.md in the repo for the self-review.

For developers

Brand, chains, and assets live in config.js (single source of truth):

window.APP_CONFIG = {
  brand: "Giftfolio", ticker: "GIFT",
  onchain:      { escrow, chain: {chainId: 4663}, tokens },  // stocks (RH Chain)
  onchainCards: { escrow, chain: {chainId: 137}, collection }, // Courtyard (Polygon)
  paidPacks:    { backend, prices, chain, usdc },   // buy-on-demand packs
  packs:  [ /* tiers: { id, name, tag, hue, min, max } */ ],
  stocks: [ /* { t: "NVDA", name: "NVIDIA" } */ ],
};

Contracts (Foundry, all tested): contracts/StockDropEscrow.sol, CardDropEscrow.sol, PackVendor.sol, PackVendorCR.sol (commit-reveal variant). The pack backend is under server/. Card data via /api/cards, stock prices via /api/prices.

FAQ

Do I need to own the card to gift a pack?

No. Packs are buy-on-demand, you pay and the card is bought for you and sealed into the link.

Do recipients need a wallet or gas?

They need a wallet address to receive the asset. For pack cards, claiming is gasless, no gas needed.

What if a link is never claimed?

Nothing is lost, the sender can refund it at any time. Packs that can't be filled auto-refund.

Can a link be claimed twice?

No. Each gift is single-use and marked claimed on-chain.

← Back home