# 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.

**SuiPump V3 is live.** Since 2026-10-31 every new token launches on SuiPump V3, the V3 (V18 lineage) contracts at package version 2, with holder rewards. V17 and V16 tokens keep trading and stay indexed; the box below lists the ids of all three. Section [10](https://suipump.org/integrations#s10) shows a SuiPump V3 launch and section [11](https://suipump.org/integrations#s11) has the SuiPump V3 ids, events and holder rewards.

**On 2026-10-14 every V17 and V16 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. New tokens launch on SuiPump V3 (section [10](https://suipump.org/integrations#s10)).

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

SuiPump V3 call (package version 2) `0x511114ba2a5af7bb6b21ab243fb4d2fd8b23105ae7be086785abd2916ea2d99b`

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); for SuiPump V3, section [11](https://suipump.org/integrations#s11)).

SuiPump V3 type `0x96a8523e464fdb6c1bb267404621cf47fcd3234d168b724b660995a420b439e3`

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)
[11 SuiPump V3](https://suipump.org/integrations#s11)

## <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.

### Three 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. The third is here: since 2026-10-31 every new token launches on SuiPump V3, the V3 (V18 lineage) contracts. Its ids, which work the same way, are in section [11](https://suipump.org/integrations#s11).

### 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 call through the latest write target listed here; new tokens launch on SuiPump V3 (section 10). 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 - legacy, no new launches, existing curves trade - 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

```
# all three forms are live on mainnet and stay valid forever
```

```
# plain: V17 and V16 coins after the rename, and every SuiPump V3 coin that is not branded
0x90cadc4e54a500a0c930f86fffb4f2c41427b232e2642d64210c1eba153514c3::suipump::SUIPUMP
# coins minted before the rename
0xc31ac634400fb3290aaaa4cd95e2d0002280859a000e25bf82c7f6393b3d14d2::template::TEMPLATE
# branded: live since 2026-11-01, SuiPump V3 only (new launches may still be plain)
0xddc2822f48403c04d5234147938af49270b8281b3ab36e0b72613106df5fddac::suipump_six::SUIPUMP_SIX
```

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. Branded coin types are live on mainnet since 2026-11-01, and both SuiPump V3 forms stay live for new launches. A launch from the app is branded, ::suipump_\<ident>::SUIPUMP_\<IDENT> (last line above), when the issuer confirms the ident rules, and plain ::suipump::SUIPUMP otherwise. A partner launch that follows section 10 keeps the module name suipump, so it is plain. So a plain ::suipump::SUIPUMP module does not tell you a coin's age or how it was launched. 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 the coin type off the curve (the T in its Curve\<T> type, or `tokenType` on 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 every V17 and V16 change; 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. These are the V17 and V16 shares; SuiPump V3 tokens split the same 1.00% differently (section [11](https://suipump.org/integrations#s11)).

## <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

Every new token launches on SuiPump V3. 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 SUIPUMP V3 LAUNCH (MAINNET)

- `call package` - V3 (V18 lineage) contracts, package version 2 - `0x511114ba2a5af7bb6b21ab243fb4d2fd8b23105ae7be086785abd2916ea2d99b` - every SuiPump V3 call in Tx2 names this.

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

- `LaunchIssuerRegistry` - shared, initialSharedVersion 1062525469 - `0x12390146d8bfd33f07890fc60477c91eeb93196ce551c59dbd2f52c7f318c884` - create_and_return argument 2.

- `PriceConfig` - shared, initialSharedVersion 1062525469 - `0xb3e2b3afaad92555a62b26559ab5ff69ee295fa618fdc1971d37f96dce51ddfc` - buy argument 5 and create_vault argument 6.

- `RewardRegistry` - rewards_core, shared, initialSharedVersion 1 - `0x1075aa5268fd108c7273f9978aec274679161e6e8b9782ae26422d08fbe555ec` - create_vault argument 1 (holder rewards, optional).

- `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` (a coin from this flow), and app launches may also be ::suipump_\<ident>::SUIPUMP_\<IDENT> (section 06), 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**: the ticket check in module authority aborts with code 65 unless the sender is the ticket's recipient, and with code 64 once the ticket has expired. Pool protection is required on SuiPump V3: share_curve refuses a curve that does not hold its pool creation cap (code 97), so leave out none of steps 3 and 4, and add step 5 when Q is USDC or USDSUI. In one transaction, in this order (every call package step is in module `curve` unless another module is named):

- `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 and, like any failed call, takes all of Tx2 with it: if the simulation fails here, leave this step out.

- `3. pool protection` - moveCall - `0x260693ec785a6e6c9d81d58c7d2ff72f1288ae0fa6a9725abe05a6478b11f084::factory::mint_pool_creation_cap`\<T>(GlobalConfig, Pools, TreasuryCap\<T>) returns a pool creation cap. It must come before step 6, which takes the TreasuryCap by value.

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

- `5. pool protection` - only for a USD quote - The same call with \<T, Q> when the graduation quote Q is USDC or USDSUI (below). Always T first.

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

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

- `6. create` - moveCall - call package `::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, the CoinMetadata\<T> (the name and symbol are read from it), 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 45% of the 1% trade fee), graduation target (pass 0, which means Cetus, the only target: any other value aborts with code 10), anti-bot delay (0, 15 or 30: seconds after creation during which only the creator can buy), Clock.

- `7. quote` - moveCall - call package `::curve::attach_pool_creation_cap<T, Q>`(curve, Pools, the cap from 3). Q is the coin the token pairs with at graduation, recorded once: SUI `0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI`, USDC `0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC` or USDSUI `0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::USDSUI`.

- `8. dev buy` - optional - The creator's own first buy: call package `::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. The app can also lock the dev buy (module vesting, lock_tokens) and set a buyback (module buyback, set_buyback_config) here.

- `9. holder rewards` - optional - The token's rewards vault: `0x33143a0dbf61ecc464d91e401748b7cb1172215265c1734c17c26650979dab86::rewards_core::create_vault`\<T, Q>(RewardRegistry, creator cap, curve, period_ms, Clock, PriceConfig) returns the vault's address. period_ms is a whole number of hours, 1 to 168, in milliseconds. To share creator fees with holders, pick pct, a whole percent of the creator's share from 1 to 100 (20 means 20% of the 45%, so 0.09% of every trade before any buyback the creator sets, which comes out of the 45% first). Then call `::curve::update_payouts<T>`(creator cap, curve, addresses, bps, Clock) with the full new list, which replaces the old one: every payout address plus the vault address, the vault at pct x 100 bps, all bps still summing to exactly 10000. Then call `::curve::commit_holder_rewards<T>`(creator cap, curve, vault address, pct, Clock). Once committed, the share can only go up and the vault can never be replaced, so get your user's consent before you sign it for them. Section [11](https://suipump.org/integrations#s11) explains the rules.

- `10. share` - moveCall - call package `::curve::share_curve<T, Q>`(curve, Pools), 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. 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 (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.

## <a id="s11"></a>11 SuiPump V3

SuiPump V3 is the V3 (V18 lineage) contracts: a new package, not an upgrade of V17, so its types start with a different id. Every new token launches on SuiPump V3 (section [10](https://suipump.org/integrations#s10)); V17 and V16 tokens keep trading and stay indexed. On 2026-10-31 20:32:47 UTC SuiPump V3 was upgraded to package version 2 (a compatible upgrade: existing types and public function signatures are kept, function bodies may change, and new ones are added).

Two counters are in play. The package version counts the upgrades of one package (V17 is at 7, V3 at 2). The schema version is a number stored inside each shared object, which the code checks before it writes. V3 is at 2 on both. The 2026-10-14 notice at the top of this page is about V17 and V16 objects, not V3.

SuiPump V3 names its modules differently from V17 and V16. The curve and its trading, fee and graduation functions and events are in module `curve` (not `bonding_curve`). PriceConfig and its events are in module `authority`. Buyback, takeover, vesting and agent sessions are in modules `buyback`, `takeover`, `vesting` and `agent_session`. Holder rewards are a separate package, module `rewards_core` (below). Always match the full type string, package::module::Name.

The indexer in section [01](https://suipump.org/integrations#s1) tracks SuiPump V3 events: on 2026-10-31 its /health reported the V3 (V18 lineage) contracts armed and tracked. SuiPump V3 tokens are live on mainnet: for example curve `0xd133f13ddb12aa6dd316208eb02cfb5f20eb011fbb31a7c2d9f6e19ceb89756e` has type `0x96a8523e464fdb6c1bb267404621cf47fcd3234d168b724b660995a420b439e3::curve::Curve<0x9e947153158b1c79da021a0c0f4a78feca125826dd441f858e320ec586ad92c5::suipump::SUIPUMP>` (read on chain 2026-11-01). The responses in sections 02 to 05 were measured on older tokens, so check those shapes on a SuiPump V3 token. The /tokens response and every per-curve route take a SuiPump V3 curve id like any other.

### CALL ONE ID, MATCH TYPES ON TWO

V3 (V18 lineage) CALL PACKAGE, version 2 - every moveCall target, and the prefix of types the upgrade added
`0x511114ba2a5af7bb6b21ab243fb4d2fd8b23105ae7be086785abd2916ea2d99b`

V3 (V18 lineage) TYPE PACKAGE, version 1 - curve types, coin types and first-publish events
`0x96a8523e464fdb6c1bb267404621cf47fcd3234d168b724b660995a420b439e3`

A Move type keeps the id of the package version that first defined it, forever. So `Curve<T>`, `CurveCreated`, `TokensPurchased`, `TokensSold`, `CreatorFeesClaimed`, `LpFeesHarvested`, `GraduatedToCetus`, `CurveMigrated` and every other type from the first publish keep the type package id, even when version 2 code emits them. The 12 event types the upgrade added are typed under the call package id, which is therefore both the call target and a type prefix. A call through the type package runs the version 1 code, which aborts with code 66 (EWrongVersion) when it writes a version 2 Curve or PriceConfig.

- `added by version 2, module authority` - type prefix: the call package - ProtocolLpRecipientSet, RewardsVaultWitnessSet, SeederCapIssued.

- `added by version 2, module curve` - type prefix: the call package - CreatorFeesPushed, HolderRewardsCommitted, RewardsVaultBound, ProtocolHalfPending, ProtocolLpProofSent, ProtocolHalfSeeded, LpFeeTokensPaid, LpRewardsHarvested, CreatorLpProofSent.

```
# an event from the first publish: filter on the TYPE package
events(filter: { type: "0x96a8523e464fdb6c1bb267404621cf47fcd3234d168b724b660995a420b439e3::curve::CreatorFeesClaimed" })

# an event added by version 2: filter on the CALL package
events(filter: { type: "0x511114ba2a5af7bb6b21ab243fb4d2fd8b23105ae7be086785abd2916ea2d99b::curve::CreatorFeesPushed" })
```

You need both ids in your filters. Each event type matches under exactly one of them: a first-publish event filtered on the call package returns nothing, and a version 2 event filtered on the type package returns nothing.

Proof from chain. Transaction `RnAjtVPw31yoG6NNTghbvig7Ybi5WwLm6yPa48T8UvN` called the version 2 package and emitted one event of each kind:

```
# events of RnAjtVPw31yoG6NNTghbvig7Ybi5WwLm6yPa48T8UvN
```

```
0x96a8523e464fdb6c1bb267404621cf47fcd3234d168b724b660995a420b439e3::authority::PriceConfigMigrated
0x511114ba2a5af7bb6b21ab243fb4d2fd8b23105ae7be086785abd2916ea2d99b::authority::ProtocolLpRecipientSet

# the same filters with the other prefix:
events(filter: { type: "0x511114ba2a5af7bb6b21ab243fb4d2fd8b23105ae7be086785abd2916ea2d99b::authority::PriceConfigMigrated" })  ->  []
events(filter: { type: "0x96a8523e464fdb6c1bb267404621cf47fcd3234d168b724b660995a420b439e3::authority::ProtocolLpRecipientSet" })  ->  []
```

### VERSION 2 OBJECTS

Version 2 code writes only objects at schema version 2. The two shared objects there is only one of were migrated on 2026-10-31 in `RnAjtVPw31yoG6NNTghbvig7Ybi5WwLm6yPa48T8UvN`:

- `PriceConfig` - shared, initialSharedVersion 1062525469, schema version 2 - `0xb3e2b3afaad92555a62b26559ab5ff69ee295fa618fdc1971d37f96dce51ddfc`

- `EnclaveRegistry` - shared, initialSharedVersion 1062525469, schema version 2 - `0x7769f682cc8ea0955e2a487d0f892d2233ab24c53bf16e1c5fb430d769d28a32`

A curve created by version 2 code starts at version 2. A curve created through the type package (version 1 code still accepts a launch ticket) starts at version 1, and version 2 code refuses it until someone calls `curve::migrate_curve<T>(curve)`: anyone may call it, it can only move a curve's version up, it aborts if the curve is already current, and it emits `CurveMigrated` with curve_id, from_version and to_version. Takeover proposals, vesting locks and agent sessions carry the same number and have their own migrate functions: `takeover::migrate_proposal`, `vesting::migrate_lock` and `agent_session::migrate_session`.

### NEW FUNCTIONS YOU MAY CALL OR INDEX

All of these are in module `curve` of the call package. Arguments are listed in order; `ctx` is added by the Sui runtime. Payout shares are in basis points (100 = 1%). Cetus coin A is the first coin in the pool's type, Pool\<A, B>.

- `push_creator_fees<T>` - (curve, Clock), anyone - Pays the curve's unpaid creator fees to the curve's EXISTING payout list, split by payout shares exactly as claim_creator_fees does. There is no destination argument and the caller receives nothing unless already on the list. Nothing to pay is a no-op with no event. Aborts on a paused curve. It is not creator activity, so it does not reset the takeover inactivity clock. Emits CreatorFeesPushed (curve_id, caller, amount). A curve-phase creator fee payout is now either CreatorFeesClaimed (type package) or CreatorFeesPushed (call package): watch both.

- `harvest_lp_fees<T>` - (curve, BurnManager, GlobalConfig, Pool\<T, SUI>), anyone - After graduation, collects the fees earned by the creator position's burn proof (the receipt Cetus gives for locked liquidity; whoever holds it collects that position's fees) when that proof is held by the curve, and pays them across the payout list. On a curve graduated by version 2 code: BOTH sides in full, SUI in SUI and the token in the token, no protocol cut, no burn; emits LpFeesHarvested (type package; creator_share equals quote_total, protocol_share, tokens_burned and tokens_locked are 0) and LpFeeTokensPaid (call package; curve_id, pool_id, amount). On a curve graduated by version 1 code (one position): the version 1 split, 50% of the quote to the payout list and the rest to the protocol, the token side burned or locked, LpFeesHarvested only.

- `harvest_lp_fees_quote<T, Q>` - (curve, BurnManager, GlobalConfig, Pool\<T, Q>), anyone - The same for a curve that graduated against a USD stablecoin quote, when the token is Cetus coin A. Aborts with code 127 (ENotUsdQuote) on a SUI curve.

- `harvest_lp_fees_quote_qt<T, Q>` - (curve, BurnManager, GlobalConfig, Pool\<Q, T>), anyone - The same when the quote is Cetus coin A.

- `harvest_lp_rewards<T, A, B, R>` - (curve, BurnManager, GlobalConfig, Pool\<A, B>, RewarderGlobalVault, Clock), anyone - Collects the Cetus bonus reward coin R earned by the creator position and pays it in full across the payout list. Pool\<A, B> is the curve's own pool in its own coin order. Version 2 graduated curves only (code 145, ELegacyNoRewardHarvest, otherwise). Aborts inside Cetus when R is not one of the pool's rewarders. Emits LpRewardsHarvested (curve_id, pool_id, reward, amount).

- `harvest gate` - every harvest above - Aborts on a paused curve, a curve that has not graduated, the wrong pool, with code 110 (ENotSeeded) when the curve holds no position proof, or with code 144 (ECreatorLpProofSent) when the creator proof went to the creator's wallet at graduation (below).

- `seed_protocol_half<T, Q>` - (SeederCap, PriceConfig, curve, GlobalConfig, Pool\<T, Q>, BurnManager, Clock), SuiPump only - Needs the active SeederCap, a permission object SuiPump holds (code 136 ESeederCapRevoked otherwise): index its event, do not call it. Only when graduation CREATED the Cetus pool: the protocol position then waits in the curve (ProtocolHalfPending) and is seeded once at the pool's current price. seed_protocol_half_qt takes Pool\<Q, T>.

- `seed_protocol_half_fallback<T, Q>` - (PriceConfig, curve, GlobalConfig, Pool\<T, Q>, BurnManager, Clock), anyone - The same seed without a cap, from 24 hours after graduation (code 138 ESeedFallbackTooEarly before that) and only while the pool's price is within 1% of the graduation price (code 114 EWalkPoolOutOfBand). seed_protocol_half_fallback_qt takes Pool\<Q, T>. Every seed emits ProtocolHalfSeeded; by_cap says which path ran. The proof goes to the recipient recorded at graduation, never to the caller, and no coin of the half reaches an address.

- `migrate_curve<T>` - (curve), anyone - Raises a version 1 curve to version 2. See above.

### GRADUATION BY VERSION 2 CODE BUILDS TWO BURNED CETUS POSITIONS

As in section [02](https://suipump.org/integrations#s2), graduation mints the 200M token LP allocation. The buy that reaches the threshold still graduates the curve inline (section [07](https://suipump.org/integrations#s7)); the Cetus pool is then built by a separate, permissionless graduation transaction. Version 2 code splits the allocation into two full-range Cetus positions of nearly equal liquidity: normally 100M tokens each with the quote each needs at the graduation price, or half the quote each when that much quote is not available. Both are burned through Cetus lp_burn: the liquidity is locked forever and each burn proof can only collect what its position earns (trading fees and Cetus rewards). A curve graduated by version 1 code (for example one launched through the type package and never migrated) has one position and none of the events below.

- `creator position` - proof kept in the curve, holder share above 0% - Only when the creator committed a holder share above 0%. Its fees and rewards are then paid by the harvest functions above across the payout list, which includes the holder rewards vault. GraduatedToCetus.proof_id is this proof.

- `0% holder share` - proof sent to the creator - When the curve has no holder rewards commitment, or a committed 0%, the creator proof is transferred to the curve's creator at graduation (CreatorLpProofSent: curve_id, pool_id, proof_id, recipient). The creator then collects its fees directly from Cetus, and every harvest on the curve aborts with code 144.

- `protocol position` - proof sent to the protocol - Its proof goes to the protocol LP recipient recorded when graduation begins (ProtocolLpProofSent: curve_id, pool_id, proof_id, recipient). Read on chain 2026-10-31: `0xb7c6163f46b6c7d92be88cfca5e1cf6aa442fdbb16511730b4e961fb50b2c475`. Take the recipient from the event, not from this page. On a version 2 graduated curve the protocol earns only through this proof and takes no share of the creator position's fees.

- `pool joined or created` - one transaction or two - If the Cetus pool already existed, both positions are built in the graduation transaction. If graduation created it, the protocol half waits in the curve (ProtocolHalfPending: curve_id, pool_id, tokens, quote, sqrt_price) until a seed above runs.

### HOLDER REWARDS

A SuiPump V3 creator can share part of the creator fees with the token's holders. The rewards contract is its own package, published on 2026-10-31:

REWARDS PACKAGE (rewards_core) - create_vault, push and every rewards event
`0x33143a0dbf61ecc464d91e401748b7cb1172215265c1734c17c26650979dab86`

REWARD REGISTRY - shared, initialSharedVersion 1
`0x1075aa5268fd108c7273f9978aec274679161e6e8b9782ae26422d08fbe555ec`

- `the vault` - one per token - The creator creates it with `rewards_core::create_vault<T, Q>` (section 10, step 9). It is shared, bound to the curve for good, and Q must be the curve's graduation quote. Each period is a whole number of hours, 1 to 168, and the creator can change it with `set_frequency` from the next period on.

- `the holder share` - only goes up - The vault takes a share of creator fees by sitting in the curve's payout list at pct x 100 bps, recorded on chain by `curve::commit_holder_rewards` (HolderRewardsCommitted). A committed share can never be lowered and the vault can never be swapped. Fees are sent to the vault's address whenever the payout list is paid (claim_creator_fees or push_creator_fees), and anyone can move them into the vault with `collect`. The creator can also add other coins with `deposit_as_creator`.

- `a period` - close, post, wait, pay - Anyone closes an ended period (`close_period`, or `close_period_converting` for a USD-quote vault); 10% of the vault's SUI is set aside to pay for the payouts. The distributor then posts one Merkle root per period and coin (RootPosted). The root becomes payable 30 minutes later. Inside those 30 minutes the guardian or the creator can veto it (RootVetoed), which sends the budget back to the vault. On chain the guardian can only veto; SuiPump runs a watcher off chain that recomputes each posted root and has the guardian veto any that do not match. There is no fixed time for posting a root after a period closes; if none is posted by the end of the next period, that budget rolls over.

- `payouts` - pushed, not claimed - Holders do not claim. A payout is made only when someone calls `push` (Pushed) with that holder's Merkle proof; SuiPump runs a keeper that does this, but anyone can. The caller is repaid the root's per-payout rate from the SUI set aside when the period closed. A root can be pushed for 7 days; after that `expire_root` returns whatever was not paid to the vault, where it goes into later periods and is not kept for that holder. No share of the vault goes to the protocol.

- `who is paid` - off-chain rule version 4 - Every holder whose smallest balance across four checkpoints of the period is above zero; there is no minimum holding. Never the curve, the vault, the pool, the creator, anyone in the payout list, an agent session, or the 26 addresses the RewardRegistry excludes on chain (exchange and protocol addresses). Shares are pro rata and no holder gets more than 10% of a round. A share too small to send (under 0.01 SUI, or 0.01 USD on a USD quote), or owed to an address that has not sent a transaction yet, is kept for that address and added to its next share until it is worth sending.

- `two roles` - two caps, two wallets - The RewardRegistry names a DistributorCap `0x197c3caf03bb029aeda39aa9569fe5da04b8daf9e049caa516cab901b70943ff`, held by the wallet `0xcdfa3e5c1bb533d2b6f12b19695eacdda53f41ec8522ae72deaa3f38812cfc58`, which may only collect and post roots, and a GuardianCap `0xfa6e0f4de4c76a84edecd37a6afce4d8e9bd3299494e70f345b9e952b803dd65`, held by the wallet `0xbdb9608bc6b9e25d764696d75baf4a565049bfdf0d63eb748f3ec021dd433187`, which may only veto. To check who sent a RootPosted or a RootVetoed, compare the sender with those two wallets, never with the cap ids. A veto returns the whole budget to the vault; an expired root returns what was not paid.

- `read a round` - indexer, section 01 - GET /rewards/:vault lists a vault's periods; GET /rewards/:vault/:period returns a period's published round with every leaf (index, recipient, amount), so you can check a root yourself.

- `events` - type prefix: the rewards package - VaultCreated, PeriodClosed, RootPosted, RootVetoed, Pushed, RootExpired and the rest are typed under the rewards package above, module rewards_core.

### NAME, SYMBOL AND ICON ON V3

SuiPump V3 tokens can change their metadata too, under the same rules as section [06](https://suipump.org/integrations#s6): one edit in the 7-day launch window, then at most one change every 24 hours. Watch `curve::MetadataUpdated` and `curve::MetadataRebranded` under the SuiPump V3 type package: both were defined at its first publish.

### FEES ON V3

The same flat 1.00% per trade as section [08](https://suipump.org/integrations#s8), split differently. No share is retained in the curve.

- `creator_fee` - 45% of the fee - To the token's payout list. A buyback the creator set is taken from this share.

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

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

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

- `lp_fee` - 0% - Nothing is retained in the curve on V3.

### BREAKING-CHANGE CHECKLIST

- `1. call target` - V3 writes - Send every SuiPump V3 moveCall to the version 2 call package above. The type package runs version 1 code, which aborts with code 66 when it writes a version 2 Curve or PriceConfig.

- `2. type filters` - unchanged - Do not replace the type package with the call package in any filter or stored type. Curve\<T>, coin types and first-publish events never carry the call package id.

- `3. new events` - second prefix - Add the call package as a second prefix for the 12 event types the upgrade added, and match the full type string, package::module::Name: three of them are in module authority.

- `4. creator payouts` - two events - Count both CreatorFeesClaimed and CreatorFeesPushed for curve-phase creator fees.

- `5. LP fee harvests` - two events - On a version 2 graduated curve, read LpFeesHarvested for the quote side (protocol_share is always 0) and LpFeeTokensPaid for the token side.

- `6. graduation` - two proofs - On a version 2 graduation, expect ProtocolLpProofSent, and CreatorLpProofSent for a 0% holder share. A created pool adds ProtocolHalfPending and later ProtocolHalfSeeded.

- `7. object versions` - version 2 - PriceConfig and EnclaveRegistry are at version 2. A curve, proposal, lock or session at version 1 needs its migrate function before version 2 code accepts it.

- `8. holder rewards` - third prefix - Index rewards events under the rewards package, and treat a payout as a Pushed event: holders never send a claim transaction.

- `9. V17 and V16` - unchanged - Their tokens keep trading through the ids in section [06](https://suipump.org/integrations#s6); only new launches moved to SuiPump V3.

### CHANGELOG

- `2026-10-31` - V3 (V18 lineage) version 2 - Upgraded in `B9F7XrBoz9L5NsiQ5mJ4BYH93GMV21RECifCd9nyFnbb` (checkpoint 340254439, compatible policy, package digest `ba5fcf26ff42126301511d7368a67af282c0354db1708fe378494bcd1673f597`). PriceConfig and EnclaveRegistry migrated to version 2 in `RnAjtVPw31yoG6NNTghbvig7Ybi5WwLm6yPa48T8UvN` (checkpoint 340255039), which also set the protocol LP recipient and issued the SeederCap `0xcfb90935b46f6872883d54207cd6507a672a2b7a64e2a2f4eab1215b30ba13b2`. Holder rewards: rewards_core published in `7gScN2DpUyg3sZz6v4rWNXz5youRJ3Yj2dgvXVdkYb8K` and its vault witness pinned on the V3 PriceConfig in `9jT4dQHbz6ivJVuWvzTaAtumqJQYTrqhWUMV7hUAjMoR` (checkpoint 340265979). SuiPump V3 opened to every new launch. Sections 10 and 11 rewritten for SuiPump V3.

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.
