Tokens & Bonding Curves
Transaction History

Transaction History

Every buy and sell against a bonding curve is an on-chain contract call, indexed by Superhero and served through one endpoint with two filters. Use it to check your own fills, research a token before you buy it, or watch a specific account.

From the command line

node scripts/superhero-transactions.mjs mine [limit] [page]
node scripts/superhero-transactions.mjs token ct_<sale_address> [limit] [page]
node scripts/superhero-transactions.mjs account ak_<address> [limit] [page]
node scripts/superhero-transactions.mjs all [limit] [page]
node scripts/superhero-transactions.mjs activity ct_<sale_address> ak_<address> [limit]
CommandFilter appliedUse it for
mineyour accountConfirming your own entry price and fills
tokenone sale addressPre-trade research: is the flow organic?
accountone accountFollowing a known trader or whale
allnoneMarket pulse: what is being traded right now
activitytoken and accountChecking whether a specific whale is accumulating a specific token

Default limit is 20 and default page is 1 everywhere. mine needs AE_PRIVATE_KEY set, because it derives your address from the key; the other four take an address as an argument and need no key.

The API

All five commands are the same endpoint with different query parameters:

GET /api/transactions
  ?token_address=ct_...      sale address of the token
  &account_address=ak_...    the trader
  &includes=token            embed the full token record in each row
  &limit=20
  &page=1

Both filters are optional and combine. Omitting both gives you the market-wide feed. The response is paginated with items and meta.

A single transaction can be fetched by hash:

GET /api/transactions/by-hash?tx_hash=th_...

Fields

Each row carries these fields:

FieldTypeMeaning
tx_hashstringOn-chain transaction hash, and the row's primary key
sale_addressstringThe bonding-curve sale contract the trade hit
tx_typestringbuy, sell or create_community, nothing else
block_heightnumberKey-block height of inclusion
addressstringThe trader's address
verifiedboolWhether the indexer has re-validated the row against the chain
volumenumeric stringTokens bought or sold, in whole tokens
protocol_rewardnumeric stringProtocolDAO tokens minted to the buyer, in whole tokens. 0 on a sell
amountprice objectAE paid or received
unit_priceprice objectamount / volume, the average price you actually got
previous_buy_priceprice objectUnit price before this trade
buy_priceprice objectUnit price after this trade
sell_priceprice objectLegacy field; do not rely on it
total_supplynumeric stringToken supply after this trade, in whole tokens
market_capprice objectbuy_price × total_supply at the time of the trade
created_attimestampBlock time

Every "price object" has ae, usd, eur, aud, brl, cad, chf, gbp and xau keys, so you can read a trade in any of them without doing your own conversion. Read amount.ae, not amount.

token.* fields are present only when the request carries includes=token or an account_address filter; otherwise they are absent.

volume is a token count

volume is the number of tokens; amount is the AE. The indexer decodes them from different events:

Row fieldDecoded fromUnits
volume on a buythe community token's Mint event amountwhole tokens
volume on a sellthe token's Burn event amountwhole tokens
amount on a buyBuy(total_price, …) on the sale contractAE
amount on a sellSell(return, …) on the sale contractAE
protocol_rewardthe ProtocolDAO token's Mint event amountwhole tokens
total_supplythe Buy/Sell event's supply argument, adjusted by volumewhole tokens

amount on a buy is the fee-inclusive total. Buy's first argument is total_price, which already contains the 0.5% affiliation fee, and that is why it is the right basis for a PnL cost figure. The event's own total_supply is the supply before the trade, so the indexer adds volume on a buy and subtracts it on a sell to store the supply after.

create_community rows are buys

A token creation with an initial buy is stored as one row with tx_type: 'create_community', priced exactly like a buy. Anything counting purchases must include it; the backend's own PnL queries count tx_type IN ('buy', 'create_community'). A creation with no initial buy emits no PriceChange event and is stored with zeroes.

The unfiltered feed is scoped to the current factory: with no token_address, tokens from a superseded factory deployment do not appear.

What the CLI prints

The transactions script reduces each row to:

function formatTx(tx) {
  return {
    type: tx.tx_type,
    hash: tx.tx_hash,
    account: tx.address,
    token_name: tx.token?.name,
    token_symbol: tx.token?.symbol,
    sale_address: tx.sale_address || tx.token?.sale_address,
    amount_ae: tx.amount?.ae,
    unit_price_ae: tx.unit_price?.ae,
    buy_price_ae: tx.buy_price?.ae,
    sell_price_ae: tx.sell_price?.ae,
    timestamp: tx.created_at,
    block_height: tx.block_height,
  };
}

Pre-trade research

node scripts/superhero-transactions.mjs token ct_<sale_address> 30

Many small buyers suggests organic momentum; one large buyer suggests a pump with higher exit risk. On a bonding curve this distinction is sharper than on an order book: a single large buy permanently moves the price for everyone, because the supply genuinely increased. The elevated price sits there until someone sells, whether or not anyone else wanted the token at that level. A chart that has gapped up on one transaction is telling you about one person's position, not about demand.

Three things to count in the output:

  1. Distinct accounts. Thirty trades from four addresses is four opinions, not thirty.
  2. Buy-to-sell ratio. A wall of buys with no sells can mean conviction, or it can mean nobody has been able to exit yet.
  3. Clustering in time. Trades evenly spread over days look different from trades in one minute, and the second pattern often has one account behind it.

Cross-check whatever you conclude against the holders tab. See Reading a Token Page.

Verifying a transaction on-chain

Every row carries a tx_hash. Superhero's indexer is convenient but it is not the chain; to verify a trade independently, open the hash in aeScan (opens in a new tab). What to check:

  • The contract called is the sale address you expected.
  • The caller is the account the indexer attributed it to.
  • The amount attached matches the AE you think moved.
  • The events (Buy or Sell on the sale contract, Mint or Burn on the token contract) carry the executed figures, which is what you want if a quote and a fill disagreed.

The same applies to a contract address: paste a sale_address into the explorer and you can read the deployed code and call its read-only entrypoints directly.

Diagnosing a failed trade

GET /api/debug/failed-transactions is a diagnostic endpoint with no UI behind it. It takes no parameters, is not scoped to an account, and returns at most 500 rows, newest first:

FieldMeaning
hashThe transaction hash the indexer could not process
retriesHow many times it has been retried
errorThe error message
error_traceThe stack trace
created_atWhen it first failed

These are transactions the indexer failed to ingest, not transactions that failed on-chain. A reverted buy never reaches it. A retry job runs every 30 minutes and drops a row once it succeeds or once retries passes 10, so the table is a rolling backlog rather than a history. If your trade is on aeScan but missing from /api/transactions, this is where to look for it.

First steps when a trade did not go through:

SymptomAbort reason to look forLikely cause
Sell reverted, tokens still heldMINIMAL_RETURN_NOT_MEETThe curve moved below your floor
Sell reverted, tokens still heldNoneNo allowance granted to the sale contract
Buy revertedAE_AMOUNT_NOT_SUFFICIENTAttached AE below price(count) after the curve moved; tolerance too tight
Buy or sell revertedMINIMUM_TOKENS_REQUIREDcount below 10^12, almost always whole tokens passed where decimal units were expected
Nothing in your transaction list, nothing on-chainNoneThe transaction was never signed or never broadcast. Check the wallet
On-chain but not in the listNoneIndexer lag, or an ingest failure. Check /api/debug/failed-transactions

See Buying & Selling for the allowance and slippage mechanics behind the first two rows.

Related