# SuiPump Integrations

> Generated at build from the page at https://suipump.org/integrations . Same content, plain markdown.

Integrations - .

Public read-only API for SuiPump bonding curves on Sui mainnet.

No key, no signup, CORS open. Every response on this page was read live on 2026-09-15.

**On 2026-10-14 every SuiPump curve was migrated to VERSION 2.** Reads are unaffected. On a migrated curve, a transaction that writes it through an older package id aborts with code 66 (EWrongVersion); section [06](https://suipump.org/integrations#s6) lists the few functions that are exempt. Launch new tokens through the V17 call id.

**CALL THESE (the moveCall target):**

V17 call (package version 7) `0xb90fc5f4cd6558a63e0d99fee3ed67c1893e65b21d0a19c126885f5e45ba78ac`

V16 call (package version 6) `0x95176456936f1eae611fb00cb3353b43e7184631e400ff3c1efe446955e245f6`

**MATCH CURVE AND EVENT TYPES WITH THESE (never call them):**

Your curve's type starts with one of these. Types added by a later upgrade keep that upgrade's id (section [06](https://suipump.org/integrations#s6)).

V17 type `0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0`

V16 type `0x7b4163d17ce18b386ee50929ba48fa0a2ecb60304df4b07e26835aa18617cda2`

[01 Base URL and conventions](https://suipump.org/integrations#s1)
[02 Supply - start here](https://suipump.org/integrations#s2)
[03 Token list](https://suipump.org/integrations#s3)
[04 Per-curve state](https://suipump.org/integrations#s4)
[05 Trades feed](https://suipump.org/integrations#s5)
[06 Identifiers](https://suipump.org/integrations#s6)
[07 Graduation semantics](https://suipump.org/integrations#s7)
[08 Fees](https://suipump.org/integrations#s8)
[09 Rate limits and polling](https://suipump.org/integrations#s9)
[10 Launching a token](https://suipump.org/integrations#s10)

## <a id="s1"></a>01 Base URL and conventions

Everything below is `GET`, needs no key and no signup, and answers with `access-control-allow-origin: *`, so it is callable straight from a browser. All ids are full 66-character hex; nothing is ever truncated. All SUI figures named `*_sui` are whole SUI as JSON numbers; all figures named `*Supply`, `sui_in`, `tokens_out` and friends are atomic units as decimal STRINGS - parse them as big integers, never as doubles.

```
BASE  https://suipump-main-web.onrender.com
```

The site itself is suipump.org. This host serves the data.

### Caching

The list and stats routes are background-refreshed and served from an edge cache. Measured headers on `/tokens`:

```
curl -sD - -o /dev/null https://suipump-main-web.onrender.com/tokens
```

```
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
access-control-allow-origin: *
cache-control: public, max-age=10, stale-while-revalidate=10
content-encoding: br
etag: "RIRAqes4_9u9NjryXUlgVaY6VHE"
age: 7
x-cache: HIT
vary: Accept-Encoding
```

Brotli at the origin. ETag is supported and returns a real 304.

```
curl -so /dev/null -w '%{http_code} %{size_download}B\n' \
  -H 'If-None-Match: W/"RIRAqes4_9u9NjryXUlgVaY6VHE"' https://suipump-main-web.onrender.com/tokens
```

```
304 0B
```

Send the ETag back and you transfer nothing. Please do this.

## <a id="s2"></a>02 Supply - start here

`GET /curve/:curveId/supply` is the endpoint to integrate first, because it is the one you do not have to trust us for. It reads the coin's own TreasuryCap on chain and returns the read-back instructions with the number.

```
curl -s https://suipump-main-web.onrender.com/curve/0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4/supply
```

```
{
  "coinType": "0x90cadc4e54a500a0c930f86fffb4f2c41427b232e2642d64210c1eba153514c3::suipump::SUIPUMP",
  "curveId": "0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4",
  "packageId": "0x90cadc4e54a500a0c930f86fffb4f2c41427b232e2642d64210c1eba153514c3",
  "decimals": 6,
  "totalSupply": "798999613064031",
  "totalSupplyFormatted": "798999613.064031",
  "circulatingSupply": "591891619371725",
  "graduated": false,
  "source": "onchain:TreasuryCap-DOF.total_supply.value",
  "readAtMs": 1789521547322,
  "verifyWith": "Read shared object 0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4
   on Sui mainnet, list its dynamic object fields, and open the child object keyed by
   bonding_curve::TreasuryCapKey - that child is the TreasuryCap, and its total_supply.value
   is totalSupply in base units. circulatingSupply is that same number minus the unsold
   token_reserve still held by the curve. No SuiPump service is involved in either figure."
}
```

- `totalSupply` - string, atomic - Minted supply, from the TreasuryCap. Divide by 10^decimals.

- `circulatingSupply` - string, atomic - totalSupply minus the unsold token_reserve still in the curve.

- `decimals` - number - 6 for every coin the launch template has minted. Read it, do not assume it.

- `graduated` - boolean - See section 07.

- `source` - string - Where the figure came from. Today always the on-chain TreasuryCap.

- `verifyWith` - string - Prose instructions to reproduce both figures from chain alone.

On a graduated curve `circulatingSupply` equals `totalSupply`, because the curve holds no unsold reserve any more, and totalSupply is higher than 800M because graduation mints the 200M LP allocation:

```
curl -s https://suipump-main-web.onrender.com/curve/0x6fd7e91a8fac8851e0172e613a167d55724c0a8dc4e177139b26329b5c675fb2/supply
```

```
{ "totalSupply": "921055993153487", "circulatingSupply": "921055993153487",
  "graduated": true, "decimals": 6, ... }
```

## <a id="s3"></a>03 Token list

`GET /tokens` returns every curve ever launched, newest first, as a JSON array. Measured 2026-09-15: 485 rows, 142,936 bytes brotli-compressed, 0.57 s.

**There is no pagination.** `?limit=2` returns all 485 rows - measured. Do not write a paging loop against it; fetch the array, diff it against your own copy, and rely on the ETag above so an unchanged list costs you nothing.

```
curl -s --compressed https://suipump-main-web.onrender.com/tokens
```

```
[ {
  "curveId": "0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4",
  "creator": "0x...",
  "name": "BAG", "symbol": "BAG",
  "description": "...", "iconUrl": "https://...",
  "tokenType": "0x90cadc4e54a500a0c930f86fffb4f2c41427b232e2642d64210c1eba153514c3::suipump::SUIPUMP",
  "packageId": "0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0",
  "createdAt": "...", "graduationTarget": 0, "antiBotDelay": 0,
  "initialSharedVersion": "988270401",
  "bundleScore": ..., "bundleLabel": ..., "bundleReason": ...,
  "graduated": false, "poolId": null, "balanceManagerId": null,
  "stats": { ... see section 04 ... }
}, ... 484 more ]
```

- `curveId` - string, 0x+64 - The shared Curve object. This is the row's primary key.

- `tokenType` - string - Fully-qualified coin type. See section 06 - its module name varies; read it, never build it.

- `packageId` - string, 0x+64 - The BONDING CURVE package, not the coin's. See section 06.

- `initialSharedVersion` - string - Needed to build a shared-object reference in a PTB.

- `graduationTarget` - number - 0 Cetus, the only target offered at launch. 1 (DeepBook) and 2 (Turbos) are values some older curves store: neither is offered any more. A stored 1 graduates to Cetus. A stored 2 does not graduate to Cetus: no pool opens for it while its venue is under review. Stored at LAUNCH - it is intent, not an outcome.

- `graduated` - boolean - Authoritative. Section 07.

- `poolId` - string | null - Null until the pool is recorded, INCLUDING for a short window after graduated flips true.

Update frequency: the underlying rows are written by the indexer as events land, and the response is edge-cached for 10 s with a further 10 s of stale-while-revalidate. Polling faster than every 10 s returns you the same bytes.

## <a id="s4"></a>04 Per-curve state

`GET /token/:curveId/stats`, also embedded as `stats` on every `/tokens` row.

```
curl -s https://suipump-main-web.onrender.com/token/0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4/stats
```

```
{
  "curve_id": "0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4",
  "volume_sui": 53769.556535805954, "volume_24h": 8368.370462715999,
  "trades": 838, "buys": 530, "sells": 308,
  "last_trade_time": "1789520693401",
  "last_price": 0.000020610907587917842,
  "first_price": 0.000023380181968807403,
  "start_price": 0.0000040717614165890025,
  "reserve_sui": 5460.681392852,
  "grad_threshold_sui": 9000,
  "token_reserve": 207107993.692306,
  "creator_fees_sui": 216.04434264400018,
  "comment_count": 6,
  "updated_at": "1789520696882",
  "sparkline24h": [ { "t": 1789435653411, "p": 0.0000233801819688074 }, ... ],
  "live_price_sui": null, "live_mcap_sui": null, "live_volume_sui": null, ...
}
```

- `reserve_sui` - number, whole SUI - SUI currently in the curve. This is the progress numerator.

- `grad_threshold_sui` - number, whole SUI - The graduation target for THIS curve. 9000 while the price relayer is idle; it is dynamic, so read it rather than hardcoding.

- `token_reserve` - number, whole tokens - Unsold tokens still in the curve.

- `last_price` - number - SUI per token, reserve-derived. first_price uses the same definition, so a delta of the two is meaningful.

- `live_*` - null | number - Post-graduation figures read from the DEX pool. Null on a curve that has not graduated, and null is "not known", never zero.

Progress toward graduation is `reserve_sui / grad_threshold_sui`. Do not cap it at 100% yourself and do not infer graduation from it - a curve can sit at 99.97% for an hour, and a curve can graduate on the token side without the reserve reaching the target at all (section 07).

## <a id="s5"></a>05 Trades feed

`GET /token/:curveId/trades?limit=N` returns the raw chain events, newest first. The `data` object is the Move event's own `parsedJson`, passed through untouched - atomic-unit strings, field names as the contract spells them.

```
curl -s 'https://suipump-main-web.onrender.com/token/0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4/trades?limit=1'
```

```
[ {
  "data": {
    "buyer": "0x9cdf1deed92b6ef1b96655ec146dece246864b603b596df01e829b4af882bc0b",
    "sui_in": "1000000000",
    "tokens_out": "48359129727",
    "tail_refund": "0",
    "curve_id": "0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4",
    "creator_fee": "4000000", "protocol_fee": "2500000",
    "airdrop_fee": "2500000", "lp_fee": "1000000",
    "referral": null, "referral_fee": "0",
    "new_sui_reserve": "5460681392852",
    "new_token_reserve": "207107993692306",
    "grad_threshold_used": "9000000000000"
  },
  "timestamp_ms": "1789520693401",
  "event_type": "0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0::bonding_curve::TokensPurchased"
} ]
```

`new_sui_reserve` and `new_token_reserve` are the curve state AFTER this trade, so a feed consumer never needs a separate state read to stay current. Sells carry `::TokensSold` with `sui_out` in place of `tokens_out`.

### Subscribing

`GET /stream?curveId=:curveId` is a server-sent-event stream. It opens with a `connected` frame and then pushes a frame per event.

```
curl -sN 'https://suipump-main-web.onrender.com/stream?curveId=0xdcb613392b937be846b0c6fda49456342eb7a8ae4c2cb493249e72b32c65bfb4'
```

```
content-type: text/event-stream
cache-control: no-cache

data: {"type":"connected","id":9331}
```

Prefer this to polling a single curve.

`tail_refund` is worth reading even if you ignore the rest: on the buy that crosses the graduation threshold it is non-zero, and the buyer's effective spend is `sui_in - tail_refund`. Treating `sui_in` as the traded amount overstates volume on exactly the trade that matters most.

## <a id="s6"></a>06 Identifiers

**packageId means two different things on two endpoints.** On `/tokens` it is the BONDING CURVE package. On `/curve/:id/supply` it is the COIN TYPE's package. They are different values for the same token and an integrator who assumes one will break on half the board.

### The three ids, and which is stable

- `curveId` - stable forever - The shared Curve object id. One per token, never changes, never reused. USE THIS AS YOUR KEY.

- `tokenType` - stable forever - The Move coin type. Immutable by construction - a Move type cannot be restated.

- `packageId` - stable per lineage - The package that DEFINES the curve type. Stable for a given curve, but there is more than one live value.

### Two curve lineages are live on mainnet

```
# distribution of /tokens[].packageId, measured 2026-09-15
```

```
0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0   396 curves
0x7b4163d17ce18b386ee50929ba48fa0a2ecb60304df4b07e26835aa18617cda2    89 curves
```

These are separate LINEAGES, not versions of one thing. A newer lineage is a fresh publish with its own type identity: a curve from one is never typed under the other, and its events are never typed under the other either. If you filter chain events by package id, you must carry both, and you must expect a third.

### Current packages: the identity id is not the call target

Within a lineage, a package can be UPGRADED, so each lineage has more than one id and they do different jobs. Types defined at first publish keep the identity id; types added by an upgrade keep the id of the version that introduced them; calls target the latest package. A Move struct is typed under the package that first defined it, and a MoveCall executes the bytecode stored at the address it names, so the id you use to recognise a curve and the id you use to call it are not the same id. If you only read, you only ever need the identity id, which is what `/tokens[].packageId` gives you, plus the handful of upgrade-introduced type ids listed below. If you call, name the latest package. Curves, takeover proposals, vesting locks and PriceConfig carry a schema VERSION. Since the VERSION 2 upgrade, the latest package of each lineage accepts them at VERSION 1 and 2, and migrate_curve, migrate_proposal, migrate_lock and migrate_price_config raise them to 2. AgentSession and EnclaveRegistry are outside VERSION 2 and are not migrated. On a migrated Curve, TakeoverProposal, VestingLock or PriceConfig, a call through a package version older than V17 version 7 or V16 version 6 that writes that object aborts with code 66 (EWrongVersion), except reclaim_vote and return_pool_creation_cap, which are not version-gated, and resolve_takeover, which checks only the Curve's version: migrating the Curve is what blocks older packages from resolving. Launching through an older package still creates objects at VERSION 1, so always launch and call through the latest write target listed here. VERSION bumps for feature changes get 30 days' written notice. Security fixes may ship with 72 hours' notice, announced to integrators directly with the new package ids.

V17 - active, all new launches - version 7

IDENTITY - events, coin types, type filters
`0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0`

LATEST - the moveCall target
`0xb90fc5f4cd6558a63e0d99fee3ed67c1893e65b21d0a19c126885f5e45ba78ac`

version 7 published 2026-10-14 19:09:34 UTC in 5QYXroz3TVVzgd6yopcvMPRFLEJHD7YxoopbkRqn4Hj6 - source [https://github.com/cacoandrade455/suipump-contracts/tree/main/contracts-mainnet-v2](https://github.com/cacoandrade455/suipump-contracts/tree/main/contracts-mainnet-v2)

V16 - legacy, no new launches, existing curves trade - version 6

IDENTITY - events, coin types, type filters
`0x7b4163d17ce18b386ee50929ba48fa0a2ecb60304df4b07e26835aa18617cda2`

LATEST - the moveCall target
`0x95176456936f1eae611fb00cb3353b43e7184631e400ff3c1efe446955e245f6`

version 6 published 2026-10-14 19:10:54 UTC in G5bCrNLfEMtJZddmVkChqE4CX3xnT7AfmDrWvNHqHzjT

### Types introduced by upgrades keep the introducing version's id

These are the only types of either lineage whose prefix is not the identity id. A filter on one of them must name the id shown here, the id of the version that introduced the type (the latest id at the time the type was introduced): the identity id finds nothing, and so does any other version's id. These ids are type prefixes, never call targets.

read from typeOrigins at each lineage's latest package (V17 version 7, recorded 2026-10-14; V16 version 6, recorded 2026-10-14); every other type of both lineages is at its identity id

V17 bonding_curve::MetadataRebranded - introduced in version 4
`0x90dcf94efae2f8ab990e564ccaa4f83b32326023ad222cd29d86bf15f0e1fbef`

V17 bonding_curve::PoolCreationCapKey - introduced in version 6
`0xbd8b60efb98b650e369d3226c4d9de37af5eb9e7a57661e3e43df04afd2185d9`

V17 bonding_curve::PoolCreationCapLoan - introduced in version 6
`0xbd8b60efb98b650e369d3226c4d9de37af5eb9e7a57661e3e43df04afd2185d9`

V17 bonding_curve::PoolCreationCapMinted - introduced in version 6
`0xbd8b60efb98b650e369d3226c4d9de37af5eb9e7a57661e3e43df04afd2185d9`

V17 bonding_curve::PoolCreationCapAttached - introduced in version 6
`0xbd8b60efb98b650e369d3226c4d9de37af5eb9e7a57661e3e43df04afd2185d9`

V17 bonding_curve::CetusPairProtection - introduced in version 6
`0xbd8b60efb98b650e369d3226c4d9de37af5eb9e7a57661e3e43df04afd2185d9`

V17 bonding_curve::PoolCreationCapLent - introduced in version 6
`0xbd8b60efb98b650e369d3226c4d9de37af5eb9e7a57661e3e43df04afd2185d9`

V17 bonding_curve::PoolCreationCapReturned - introduced in version 6
`0xbd8b60efb98b650e369d3226c4d9de37af5eb9e7a57661e3e43df04afd2185d9`

V16 bonding_curve::MetadataRebranded - introduced in version 4
`0xbf02a156f97aee82563a6853a0b567bb3c1dcaa47c43a30997bdae4dd4a21196`

V16 bonding_curve::TokensBurned - introduced in version 2
`0xb1ad998007f93af6b29bd60b345bb7ed7fd6b3e7381ca08c88051e615b174975`

V16 bonding_curve::PoolCreationCapKey - introduced in version 5
`0xa8872784a627e640d2070841d14a2d1566ec66d882b28e3f9ef3ddfd5bc7dfc4`

V16 bonding_curve::PoolCreationCapLoan - introduced in version 5
`0xa8872784a627e640d2070841d14a2d1566ec66d882b28e3f9ef3ddfd5bc7dfc4`

V16 bonding_curve::PoolCreationCapMinted - introduced in version 5
`0xa8872784a627e640d2070841d14a2d1566ec66d882b28e3f9ef3ddfd5bc7dfc4`

V16 bonding_curve::CetusPairProtection - introduced in version 5
`0xa8872784a627e640d2070841d14a2d1566ec66d882b28e3f9ef3ddfd5bc7dfc4`

V16 bonding_curve::PoolCreationCapLent - introduced in version 5
`0xa8872784a627e640d2070841d14a2d1566ec66d882b28e3f9ef3ddfd5bc7dfc4`

V16 bonding_curve::PoolCreationCapReturned - introduced in version 5
`0xa8872784a627e640d2070841d14a2d1566ec66d882b28e3f9ef3ddfd5bc7dfc4`

Proof from chain, so you do not have to take the rule on trust. A V17 sell executed on 2026-09-21 21:00:06 UTC named the package that was the latest at the time (version 5) in its MoveCall and emitted an event typed under the identity package. Filtering events on the latest package id finds nothing.

```
# transaction EEC8Ux9gPzV8P7Bcx3pUwAZerrNpPNgPjpKpAL195Zxm, checkpoint 325353047
```

```
MoveCall  0x70a9b28a28c028e5ab9ed9fefec26f1fc43918bc094d60527b8968fb4f5ab0b5::bonding_curve::sell   (version 5)
event     0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0::bonding_curve::TokensSold

# the same event filter with the called package id in place of the identity id:
events(filter: { type: "0x70a9b28a28c028e5ab9ed9fefec26f1fc43918bc094d60527b8968fb4f5ab0b5::bonding_curve::TokensSold" })  ->  []
```

Resolve the latest package for any identity id from chain yourself rather than from a page. Sui GraphQL answers `package(address:)` with the newest version whichever version's id you pass, and `packageVersions(address:)` lists every version with its id, which is also how you find the id of the version that introduced a newer struct.

```
# POST https://graphql.mainnet.sui.io/graphql
{ package(address: "0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0") { address version } }
```

```
{ "data": { "package": { "address": "0xb90fc5f4cd6558a63e0d99fee3ed67c1893e65b21d0a19c126885f5e45ba78ac", "version": 7 } } }
```

Source for the V17 bytecode: [https://github.com/cacoandrade455/suipump-contracts](https://github.com/cacoandrade455/suipump-contracts) (contracts-mainnet-v2).

### Coin-type module names vary, and every form is permanent

```
# the first two are live today and stay valid forever
```

```
0x90cadc4e54a500a0c930f86fffb4f2c41427b232e2642d64210c1eba153514c3::suipump::SUIPUMP
0xc31ac634400fb3290aaaa4cd95e2d0002280859a000e25bf82c7f6393b3d14d2::template::TEMPLATE
# NOT LIVE YET - branded form, from an upcoming release:
0x<package>::suipump_pepe::SUIPUMP_PEPE
```

The coin template's module was renamed part-way through, and a coin's type can never change, so the coins minted before the rename keep `::template::TEMPLATE` forever. An upcoming release adds a branded form, ::suipump_\<ident>::SUIPUMP_\<IDENT> (last line above). The ident is taken from the coin's symbol, or from its display name when the symbol has no usable letters or digits. That tells you what to expect, not how to build it: never construct a coin type yourself. Read `tokenType` off the row and split it.

### Name, symbol, description and icon can change

Metadata is not fixed at launch. The wallet holding a token's active CreatorCap can change the name, symbol, description and icon of its `0x2::coin::CoinMetadata<T>` on chain, on both lineages, under two rules: one edit inside the launch window (7 days on the first lineage, 24 hours on the second), plus at most one change every 24 hours for the token's whole life (live on the first lineage since 16 September 2026 and on the second since 21 September 2026). Key everything on `tokenType` or `curveId`, never on the symbol, and treat name, symbol and icon as display values you refresh. Every change on either lineage emits `bonding_curve::MetadataUpdated` typed under the two identity ids above, so watching that one event on both ids catches all of them; the once-every-24-hours path also emits `MetadataRebranded` with the old and new name, symbol and icon. That struct was introduced by an upgrade, version 4 on both lineages, so its type prefix is the introducing version's id, listed above under types introduced by upgrades, and neither the identity id nor, on the first lineage, the latest id. Check that a prefix is a version of the lineage before you trust the struct. The Curve object's own name and symbol fields, and `CurveCreated`, are the launch values and never change. Sealing a graduated token's supply to the coin registry, possible on the first lineage only, destroys its TreasuryCap and freezes its metadata permanently; no upgrade can reverse that.

## <a id="s7"></a>07 Graduation semantics

When a curve fills, it graduates and its liquidity moves to a DEX. Three things about the mechanics will affect your product.

### The crossing buy graduates INLINE, in the same transaction

There is no separate graduation transaction to wait for. The buy that carries the reserve to the threshold sets `graduated = true` in its own execution, and that buy is itself CLIPPED: only the SUI needed to reach the threshold is swapped, and the overshoot comes back to the buyer fee-free as `tail_refund`. From that instant, every further buy and sell on the curve ABORTS.

### A curve can also graduate with its reserve short of the target

Graduation fires on *reserve at or above the threshold* OR *token_reserve drained to zero*. A progress bar computed only from the reserve will sit below 100% on a token that has already graduated. Trust `graduated`, never the ratio.

**Handle "graduated, pool not yet recorded".** `graduated` flips true in the buy transaction. `poolId` is written by a separate transaction that follows within roughly a minute. In that window a token is graduated with `poolId: null` and with `live_*` fields still null. A terminal that assumes a graduated token has a pool will show a broken page for that window on every single token, on the most-watched minute of its life. Render "graduated, pool pending" and poll.

### After graduation, we are not the source

A graduated token trades on a third-party DEX outside SuiPump's control. The `live_*` fields are our best-effort read of that pool and are null when we do not have one. `poolId` is the id you should be reading directly.

## <a id="s8"></a>08 Fees

A flat **1.00%** on every curve trade, split inside the contract and emitted on every event, so you never have to model it:

- `creator_fee` - 40% of the fee - To the token's creator.

- `protocol_fee` - 25% (20% with a referral) - To the protocol.

- `airdrop_fee` - 25% (20% with a referral) - To the season airdrop bucket.

- `lp_fee` - 10% - Retained in the curve, deepening the liquidity that seeds the DEX pool.

- `referral_fee` - 10%, only with a referral - To the referrer named in the trade.

On a buy the fee is taken on the SUI input, before the swap, and on the CLIPPED amount rather than the full payment when the buy graduates the curve. On a sell it is taken on the SUI output, after the swap. The five legs always sum to exactly the fee, on both sides.

## <a id="s9"></a>09 Rate limits and polling

The indexer has no hard rate limit and no key (the launch issuer does: section 10). What there is instead:

- `/tokens, /stats` - poll at 10 s or slower - Edge-cached for 10 s. Faster polling returns identical bytes and burns your own budget. Send the ETag.

- `/token/:id/*` - poll at 5 s or slower - Per-curve routes are cheap but they are database reads.

- `/stream` - prefer this - One SSE connection per curve beats any polling interval.

- `/curve/:id/supply` - poll at 60 s or slower - This one does an on-chain read per call. Treat it as expensive.

If you need a higher volume than this comfortably supports, ask rather than scaling up quietly - we would rather give you a feed shaped for what you are building.

## <a id="s10"></a>10 Launching a token

A launch is two transactions signed by one wallet, with one call to the SuiPump issuer between them. The issuer is required: a curve cannot be created without a launch ticket, and only the SuiPump issuer can mint one. Everything else is ordinary Move calls that anyone may make. Everything in this section is on Sui mainnet. The responses shown are the shapes the issuer code returns, not a pasted live call.

- `who signs` - one wallet, both transactions - Normally your user's wallet signs Tx1 and Tx2 and becomes the token's creator: the creator cap goes to it, and the creator's fee share goes to the payout addresses it sets in Tx2. If your app signs instead, your app is the creator.

- `what it costs` - gas + 2 SUI - Gas for both transactions plus a 2 SUI launch fee paid in Tx2, plus the dev buy amount if you make one. If Tx2 never runs, only the Tx1 gas is spent, and the coin and its TreasuryCap stay in the signing wallet.

- `order` - checklist - 0. GET https://suipump-mainet-issuer.onrender.com/health: its lineagePackage must equal the type package below; if not, stop and ask on Discord. 1. Tx1 publishes the coin. 2. POST https://suipump-mainet-issuer.onrender.com/issue gets the ticket. 3. Tx2 creates the curve, within one hour of the ticket.

- `partner key and support` - Discord - Ask for a partner key, or for help with a stuck launch, on [https://discord.gg/UZ4wzDcEPN](https://discord.gg/UZ4wzDcEPN).

### IDS FOR A V17 LAUNCH (MAINNET)

- `call package` - V17 contracts, package version 7 - `0xb90fc5f4cd6558a63e0d99fee3ed67c1893e65b21d0a19c126885f5e45ba78ac` - every SuiPump call names this (they are all in Tx2).

- `type package` - type names only - `0xb205fea41ccedac051bc66498e6ca68cb802c4a6ea06da12e524bed09c80d9b0` - curve and event types start with this. Never send a call to it: it runs the original version 1 code.

- `LaunchIssuerRegistry` - shared, initialSharedVersion 974716582 - `0xb622741bfcfd6ef13b40c2d5c2adc8d796f68b3b1254fabaa571bffa3a91e875` - create_and_return argument 2 (issuer_registry).

- `PriceConfig` - shared, initialSharedVersion 974716582 - `0xaebff66fd224e4fefae2426b5cdb30c6e5c488dce5ec34a730246e8b91d44ff9` - buy argument 5 (price_cfg).

- `Clock` - shared - `0x0000000000000000000000000000000000000000000000000000000000000006`

- `CoinRegistry` - shared - `0x000000000000000000000000000000000000000000000000000000000000000c`

initialSharedVersion is the version a shared object was created at; pass it when you build a shared object reference, or let the Sui SDK look it up.

### TRANSACTION 1 - PUBLISH THE COIN

- `1. template` - fetch - `https://suipump.org/template.mv`, the SuiPump coin template.

- `2. metadata` - patch by constant index - The template's constant pool has exactly two entries. Constant 0 is the decimals (u8 6): never touch it. Constant 1 is one vector\<u8> whose bytes are four BCS-encoded strings back to back, each a ULEB128 length followed by its UTF-8 bytes, in this order: symbol (printable ASCII), name, description (up to 500 characters), icon URL (https). Overwrite constant 1 BY INDEX with `@mysten/move-bytecode-template` (in the tool's JSON, `constant_pool[1].data` is the ULEB128 of the blob's byte length followed by the blob); it must still hold the template's placeholder blob (SUIPUMP_TEMPLATE_SYMBOL, SUIPUMP_TEMPLATE_NAME, SUIPUMP_TEMPLATE_DESCRIPTION, SUIPUMP_TEMPLATE_ICON_URL) when you do. Do not use update_constants, which matches by value. Leave everything else, the module name suipump and the witness SUIPUMP included, unchanged: the issuer re-derives the bytes and refuses any difference (TEMPLATE_BYTES_MISMATCH), and an unpatched placeholder is refused SENTINEL_PRESENT.

- `3. publish` - one transaction - Publish the patched module (dependencies: 0x1, the Move standard library, and 0x2, the Sui framework), then pass the returned UpgradeCap to `0x0000000000000000000000000000000000000000000000000000000000000002::package::make_immutable`, so the coin's code can never be changed.

The coin's `init` sends `TreasuryCap<T>` (the right to mint the coin) to the publisher and creates a `CoinMetadata<T>`. Read both object ids and the coin type from the transaction's created objects. Never build the coin type as a string: older coins are typed `::template::TEMPLATE` and newer ones `::suipump::SUIPUMP`, so read it from chain.

### THE LAUNCH TICKET

The request body is one field, `{"packageId": "<package id from Tx1>"}`. Nothing else is read.

```
curl -X POST https://suipump-mainet-issuer.onrender.com/issue \
  -H "Content-Type: application/json" \
  -H "x-partner-key: <your key>" \
  -d '{"packageId":"0x<Tx1 package id>"}'
```

```
{ "ok": true, "reused": false, "lineagePackage": "0x...", "ticketId": "0x...",
  "coinType": "0x...::suipump::SUIPUMP", "recipient": "0x...",
  "expiresAt": 1793065285728, "txDigest": "..." }
```

expiresAt is Unix time in milliseconds.

The issuer host is `https://suipump-mainet-issuer.onrender.com`; "mainet" is the service's real name, not a typo. The issuer reads your package from chain and checks that it is the SuiPump template byte for byte. It takes the ticket recipient from the **on-chain owner of TreasuryCap\<T>**, never from the request: any other field you send, a recipient included, is ignored. It then signs one transaction calling `issue_launch_ticket`, which sends the ticket to that owner. The ticket expires one hour after it is minted. If a live ticket with at least five minutes left already sits at the owner, it is returned with `reused: true` and nothing is minted. A coin gets at most two tickets in total (the default); after that the issuer answers REMINT_BUDGET_EXHAUSTED and the launch needs SuiPump support, so send Tx2 while the ticket is live.

Always check the body's `ok` field, not only the HTTP status. A refusal is `{ "ok": false, "reason": "<CODE>", "message": "..." }`, usually with a 4xx or 5xx status; CONFIRM_PENDING comes back as HTTP 202 with ok: false, which means the ticket was minted but the issuer could not read it back yet. Wait 30 to 60 seconds, then ask again: usually you get that same ticket with reused: true. If the issuer still cannot read it, asking again can mint the coin's second ticket, so do not hurry this one.

- `retry` - every 10 to 15 s, up to 5 min - NOT_FOUND, TREASURY_NOT_FOUND, CHAIN_UNAVAILABLE, RATE_LIMITED, ISSUER_UNAVAILABLE, CONFIRM_PENDING, SIGNER_BALANCE_UNKNOWN. These are temporary conditions on our side. For NOT_FOUND and CHAIN_UNAVAILABLE, stop after 7 attempts in a row: an 8th failure blocks the package for 30 minutes. Still failing after 5 minutes: ask on Discord.

- `MINT_FAILED` - at most 2 more attempts - The issuer's mint transaction failed on chain. Try at most 2 more times, about 30 seconds apart, then ask on Discord: every attempt uses one of your hourly ticket slots (the per-IP ones without a key), even when it fails.

- `retry later` - after the wait it names - PACKAGE_BLOCKED (8 NOT_FOUND or CHAIN_UNAVAILABLE answers in a row block the package for 30 minutes; the body carries blockedUntil). MINT_CEILING_REACHED (the hourly ticket limit; retry when the rolling hour frees a slot, after retryAfterSec).

- `do not retry` - fix the request or ask us - Everything else, including UNAUTHORIZED, ORIGIN_NOT_ALLOWED, REMINT_BUDGET_EXHAUSTED and the template checks (WRONG_MODULE_NAME, TEMPLATE_BYTES_MISMATCH and the rest).

`GET https://suipump-mainet-issuer.onrender.com/health` reports the issuer's state; its `lineagePackage` must be the type package above, or the coin could never get a ticket, so check it before Tx1. `GET https://suipump-mainet-issuer.onrender.com/status/:packageId` returns the recorded ticket state.

- `x-partner-key` - header - Your partner key. Without one you are an anonymous caller: 10 requests per minute and 10 tickets per hour per IP (the defaults), shared with everything behind that IP, and the issuer may refuse keyless calls UNAUTHORIZED; ask for a key if it does. We keep only its SHA-256, never the key, so a lost key is replaced, not recovered. Send it from your server.

- `hourly limit` - per key - Tickets your key may mint per rolling hour, set when your key is issued. Past it, the issuer refuses with MINT_CEILING_REACHED (scope: partner).

- `requests` - 60 per minute per key - The default. Retries count toward it.

- `browser origin` - optional - One https origin of yours can be allowed to call the issuer from a browser. A key sent from any other page is refused ORIGIN_NOT_ALLOWED. A key in a public page is readable by anyone, so prefer your server.

- `per publisher` - 10 per hour - Tickets minted to one recipient address, with or without a key.

The issuer's signing key never leaves our server. You never see it, send it, or need it. It can do one thing, mint launch tickets, and our server signs only for coins that pass the template check. If it were ever compromised we would rotate it on chain, which stops every unused ticket at once; a coin caught by that gets a new ticket on request, and the new ticket counts as one of the coin's two.

### TRANSACTION 2 - CREATE THE CURVE

Sign it with the **same address that published Tx1**: in bonding_curve, create_and_return aborts with code 65 unless the sender is the ticket's recipient, and with code 64 once the ticket has expired. In one transaction, in this order:

- `1. fee` - split 2 SUI from gas - Exactly 2 SUI (2000000000 MIST; 1 SUI is 10^9 MIST), split from the gas coin, or create aborts.

- `2. registry` - moveCall - `0x0000000000000000000000000000000000000000000000000000000000000002::coin_registry::migrate_legacy_metadata`\<T>(CoinRegistry, CoinMetadata\<T>). Not required by create_and_return, but without it wallets do not show the token. Anyone may register a coin, so if it already has a CoinRegistry entry when Tx2 runs, this call aborts: leave it out.

- `3a. pool protection` - moveCall - `0x260693ec785a6e6c9d81d58c7d2ff72f1288ae0fa6a9725abe05a6478b11f084::factory::mint_pool_creation_cap`\<T>(GlobalConfig, Pools, TreasuryCap\<T>) returns a pool creation cap. This reserves the coin's Cetus pool for the curve, so nobody can open it early at a bad price.

- `3b. pool protection` - moveCall - `0x260693ec785a6e6c9d81d58c7d2ff72f1288ae0fa6a9725abe05a6478b11f084::factory::register_permission_pair`\<T, 0x2::sui::SUI>(GlobalConfig, Pools, 200u32, the cap from 3a)

- `GlobalConfig` - Cetus, shared, initialSharedVersion 1574190 - `0xdaa46292632c3c4d8f31f23ea0f9b36a28ff3677e9684980e4438403a67a3d8f`

- `Pools` - Cetus, shared, initialSharedVersion 1574190 - `0xf699e7f2276f5c9a75944b37a0c5b5d9ddfd2471bf6242483b03ab2887d198d0`

- `4. create` - moveCall - call package `::bonding_curve::create_and_return<T>` returns (curve, creator cap). Arguments in order: the ticket (by value), the LaunchIssuerRegistry, the TreasuryCap\<T> (by value; its supply must still be 0), the fee coin, name, symbol (ASCII), description, payout addresses (1 to 10, no duplicates), payout bps (hundredths of a percent, one per address, summing to exactly 10000; they split the creator's share, which is 40% of the 1% trade fee), graduation target (pass 0 = Cetus, the exchange the token moves to when the curve fills; the contract also accepts 1 and 2, which are not offered, and a curve stored with 2 never opens a pool: see graduationTarget in section 03), anti-bot delay (0, 15 or 30: seconds after creation during which only the creator can buy), Clock.

- `5. attach` - moveCall - call package `::bonding_curve::attach_pool_creation_cap<T>`(curve, Pools, the cap from 3a)

- `6. dev buy` - optional - The creator's own first buy, in the same transaction: call package `::bonding_curve::buy<T>`(curve, payment, min_tokens_out, referral, PriceConfig, Clock) returns (tokens, refund). Send both to the creator. referral is Option\<address>: your address, or none. min_tokens_out is the fewest tokens you accept; quote it by simulating the buy.

- `7. share` - moveCall - call package `::bonding_curve::share_curve<T>`(curve), then transfer the creator cap (the object that controls the token's creator settings) to the creator.

Simulate Tx2 before you sign it: building it with the Sui SDK (`tx.build` with a client) runs a simulation, which needs the live ticket and spends nothing. If Cetus refuses steps 3a, 3b and 5 (a pause or a version change on their side), build Tx2 without them and launch anyway; the curve is then left to SuiPump's pool-protection keeper. Pool protection never has to block a launch. The new curve id is in Tx2's `CurveCreated` event, field `curve_id`.

### REFERRALS

You earn 10% of the 1% trade fee (0.1% of the SUI traded) on every trade that names your address as its `referral`, the argument of buy and sell, paid in the same transaction (section [08](https://suipump.org/integrations#s8)). Name it on every trade your users make, the dev buy included. There is no referral on the launch itself: create_and_return takes none and the 2 SUI fee has no referral leg. The referral must not be the trader or the token's creator, or the trade aborts (bonding_curve code 27): if your app signs the launch, your app is the creator and cannot refer that token.

### WHAT NEEDS SUIPUMP

One thing: the launch ticket. Every launch needs one successful POST /issue per coin. If the issuer is down, limits you, or its signer runs out of gas, Tx1 can land but the curve waits for a ticket. The coin stays in the creator's wallet with its TreasuryCap and can be finished later: request the ticket again (it lasts one hour) and send Tx2, within the two-ticket limit above. Everything else needs nothing from us.

Every request in sections 01 to 09 was executed against the live mainnet indexer on 2026-09-15 and the responses are pasted verbatim. Figures move; shapes do not. If an endpoint here does not behave as documented, that is a bug and we want to hear about it.
