Social
Tipping

Tipping

A tip on Superhero is a plain AE transfer with a payload that names the post: a spend transaction from your account straight to the author's. The indexer reads the payload to attribute it. There is no fee, no escrow and no intermediary.

The transaction

sdk.spend(amountInAettos, recipientAddress, { payload })
PartValue
AmountThe AE amount converted to aettos, 18 decimals
RecipientThe post author's ak_… address, directly
PayloadTIP_POST: plus the post id with its _v3 suffix
Payload for a profile tipTIP_PROFILE
EncodingThe payload string is byte-array encoded, arriving on chain as a ba_… value

The post id is normalized before it goes into the payload: an id without _v3 gets the suffix appended, so TIP_POST:3903_v3 is the canonical form regardless of how the caller spelled it.

Because it is an ordinary spend, the AE lands in the author's balance in the same transaction. There is nothing to claim, no vesting, and no minimum enforced by the protocol.

The Tipping v3 contract is not involved: it stores posts, has no payable entrypoint and holds no balance. (A companion Tipping_v3_Getter contract adds one read helper, get_tip_by_id(c : Tipping, tip_id : int) : option(Tipping.tip), for fetching a single post without pulling the whole state.)

Practical consequences:

  • A tip cannot fail because of contract logic; it fails only if you cannot pay.
  • Attribution happens off chain, by the indexer reading the payload. If the indexer misses a tip, the AE still arrived; only the displayed total is affected.
  • Anyone can reconstruct tip attribution by scanning spend transactions to an address and decoding payloads that start with TIP_POST:.

How the indexer attributes a tip

  1. The transaction must have tx.type === 'SpendTx'. Contract calls are never tips.
  2. The payload is decoded from its ba_… form to a string. It must start with TIP_PROFILE or TIP_POST, otherwise the transaction is skipped.
  3. For a TIP_POST: payload, everything after the colon is taken as a post id and looked up directly by id. A miss leaves the tip unattached to a post.
  4. Self-tips are dropped, not stored. For a post tip that means sender == post.sender_address (tipping your own post), and for a profile tip sender == receiver.
  5. The row is keyed on tx_hash, so reprocessing the same transaction is idempotent.
  6. The amount is stored as toAe(tx.amount), a decimal AE string, not aettos.

Two things follow:

  • Tips sent outside the app count. A manually constructed spend with a TIP_POST:3903_v3 payload is indistinguishable from one the app sent, and is indexed identically.
  • There is no minimum and no dust filter. Any positive amount is stored and summed. The web composer requires a positive amount within your balance; the indexer applies no floor.

Tip from the app

The tip button sits on every post in the feed and on the post page, showing the post's current total in AE.

  1. Click Tip on a post. The modal opens with the recipient resolved to the author's display name.
  2. Enter an amount, or use a quick amount: 1, 10, 100 or 500 AE.
  3. Confirm in your wallet.

The button shows Sending, then Tipped once the transaction returns. An amount above your balance disables the button.

Why the total takes a moment

The displayed total comes from the indexer, which has to see the transaction confirm. The app adds your amount to the cached total immediately, then polls GET /api/tips/posts/{postId}/summary (5 seconds after the send, then every 3 seconds for up to 18 more attempts, roughly a minute) until the backend total reaches the expected value. If the minute elapses, the app does a final refetch and leaves the real value in place.

Read tip totals

EndpointReturns
GET /api/tips/posts/{postId}/summarytotalTips, a decimal AE string, summing every tip on the post except those sent by the post's own author. 404 if the post id is unknown.
GET /api/tips/accounts/{address}/summarytotalTipsSent and totalTipsReceived, both decimal AE strings, each excluding rows where sender equals receiver
GET /api/tipsThe paginated tip list

The post id in the path takes the _v3 suffix, matching the payload form.

GET /api/tips accepts sender, receiver, type and post_id filters, order_by of amount, type or created_at, an order_direction, and page/limit with a maximum limit of 100. Ordering by amount casts the stored string to numeric first, so it sorts by value rather than lexically. type holds the whole decoded payload (TIP_PROFILE, or TIP_POST:3903_v3), so filtering by type is an exact-string match on that, not on the bare word TIP_POST.

Tip signals also feed the popular feed: tipsAmountAE, tipsCount and uniqueTippers are three of the eight weighting inputs a reader can scale, all computed with self-tips excluded. See The Feed.

Tipping an address that has never used Superhero

The recipient is an æternity address, which exists whether or not anyone has opened it in a wallet. A tip increases that address's balance; if the key is lost, so is the AE. There is no pending-tip pool and no reclaim path.

Tips and notifications

There is no tip notification type. The closest is incoming-transfer, which fires on any incoming SpendTx, a tip included, above NOTIF_MIN_AMOUNT_AETTOS (default 0, meaning off). That type is mobile push only, so an incoming tip produces a mobile push if the recipient has a registered device, and nothing in the web bell. See Notifications.

Related

  • Posting: the ids and _v3 suffix convention the payload depends on.
  • The Feed: how tip signals influence popular ranking.
  • Notifications: why a tip does not raise a bell notification.
  • Value flow: where money moves elsewhere in the protocol.
  • Transactions: reading spend transactions and their payloads.