Ticker
PELUONLINENETSUIESCROW…SCANPELUSIUM · LIVE CHAINPELUONLINENETSUIESCROW…SCANPELUSIUM · LIVE CHAINPELUONLINENETSUIESCROW…SCANPELUSIUM · LIVE CHAINPELUONLINENETSUIESCROW…SCANPELUSIUM · LIVE CHAIN

Builder API

Use Pelusium from your own dApp to publish Cargo Well asks, list collateralized haul jobs, read the open job board, and share flight numbers (PELU-…) with couriers. Settlement and protocol fees are enforced on-chain when a job is created and delivered.

This page is for integrators (wallets, marketplaces, companion apps). Player guides live under Introduction. Interactive HTTP reference (Try it): Builder API reference.

You do not wait on world-contract A-to-B inventory to integrate. Partner asks, wallet proofs, routing, and dryRun: true work today on Stillness. Dry runs show as Preview on Cargo Well and the job board for ~15 minutes but do not lock SUI or settle. Pickup and deliver still use the same Pelusium haul steps once a real job exists.

What to build first

Your UIPelusium APINeeds on-chain SUI?
Sell on Pelusium (any storefront)POST /api/shop/partner-listings/No — wallet proof only. Use dryRun: true until you are ready to publish.
Inbound freight on a hub pageGET /api/logistics/jobs/?dropoff=<ssu>No — public read.
Buy & deliver / haulcreateHaulIntent then sign create_haul_jobYes, on confirm. Use dryRun: true first to check route and PTB args.

No Pelusium store registration. source is your app id (for example my-shop or your-app-id — pick one stable string per integration).

Overview

LayerRole
@pelusium/sdkWallet-proof helpers, HTTP calls, and Sui Programmable Transaction Blocks (create, accept, pickup, deliver).
Pelusium backendIndexes jobs, pathfinding, async listing intents, partner asks, public reads.
Move (haul_contract)Escrow, courier bond, treasury fees, delivery rules.
Your storefront  --wallet proof-->  partner listing (Cargo Well ask)
Buyer / your app --sign PTB------>  haul_contract (Sui)
                                      |
                                      v
                           Pelusium indexer  -->  job board + flight PELU-…

Base URL

All requests use the Pelusium API host shown at the top of the interactive reference. Paths such as /api/logistics/jobs/ are appended to that host (production: https://api.pelusium.world).

Install the SDK

npm install @pelusium/sdk @mysten/sui

Package: npmjs.com/package/@pelusium/sdk.

Network defaults: STILLNESS_TESTNET (packageId, protocolConfigId, haulConfigId).

Dry run (integrator test suite)

Use dry run before locking SUI on a live haul. You get the same routes and proofs with no SUI spent.

What you testHow
Payload + SSU ownership + Cargo Well rowPartner listing with dryRun: true — publishes a Preview ask
Route, fees, PTB build argscreateHaulIntent with dryRun: true, then poll until ready
Public readsGET jobs, listings, prices, flights — or click Try it on the reference

Dry-run hauls follow processing → ready / failed. They cannot be confirmed. Ready dry-run hauls appear on the job board as status: preview with flight PELU-DRY-… (TTL 24 hours on the sandbox deploy via LOGISTICS_DRY_RUN_TTL_SECONDS; default elsewhere is 15 minutes).

Dry-run partner listings write a Cargo Well Preview row (preview: true, expiresAt). Everyone sees Preview · your-app-id; use Preview Buy & deliver to start a dry-run haul (wallet proof only — no SUI locked). Preview asks do not match live bids or decrement on a real haul. Same source + externalListingId without dryRun upgrades that row to a live ask.

Sandbox click-through (preview delivery loop)

On the sandbox line, after a dry-run haul is ready, advance rehearsal stages with wallet proofs only:

StageProof action
acceptedlogistics.sandbox.accept
pickup_approvedlogistics.sandbox.approve_pickup
picked_uplogistics.sandbox.pickup
deliveredlogistics.sandbox.deliver
POST /api/logistics/intents/{intentId}/sandbox/advance/
{ "stage": "accepted", "proof": { ... } }

The shipper wallet may walk every stage for a solo demo. Another wallet may accept, becomes courier, and must perform pickup/deliver. GET /api/logistics/flights/PELU-DRY-… resolves preview flights. Confirm stays 409.

You still sign a wallet proof (shop.partnerListing.upsert or logistics.intent.create). That proof does not move funds.

import {
  buildWalletProofMessage,
  createHaulIntent,
  PELUSIUM_WALLET_PROOF_ACTIONS,
  upsertPartnerCargoListing,
  waitForLogisticsIntent,
} from "@pelusium/sdk"

const BACKEND = "https://api.pelusium.world"

// 1) Preview ask on Cargo Well (no live match)
const listingCheck = await upsertPartnerCargoListing(
  BACKEND,
  {
    source: "your-app-id",
    externalListingId: "dry-1",
    sellerWallet: wallet,
    pickupSsuId,
    pickupSystemId: 300001,
    typeId: 12345,
    quantity: 10,
    priceSui: 50,
    characterId,
    ownerCapId,
    dryRun: true,
  },
  listingProof, // action: shop.partnerListing.upsert
)
console.log(listingCheck.valid)

// 2) Validate a haul (route + buildArgs, no confirm)
const pending = await createHaulIntent(BACKEND, {
  proof: createProof, // action: logistics.intent.create
  wallet,
  characterId,
  pickupSsuId,
  dropoffSsuId,
  pickupSystemId: 101,
  dropoffSystemId: 202,
  typeId: 12345,
  quantity: 10,
  freightMist: 10_000_000,
  goodsMist: 50_000_000_000,
  requireBond: false,
  source: "your-app-id",
  dryRun: true,
})
const ready = await waitForLogisticsIntent(BACKEND, pending.intentId)
console.log(ready.status, ready.route?.summary, ready.buildArgs)

Dry run proves your integration. It does not prove in-game pickup or deliver. Those need a real signed haul and Pelusium authorized on both SSUs. On Stillness, live jobs use collateralized haul today; that is enough to test the API. Exact sealed-bag world delivery is a later game install — do not block your Sell on Pelusium button on it.

Sell on Pelusium from any storefront

Any storefront can publish an ask into Cargo Well without registering a Pelusium store. Buyers Buy & deliver; freighters run the normal haul.

  1. Seller taps Sell on Pelusium on a hangar stack (not a warehouse receipt).
  2. Seller signs shop.partnerListing.upsert (no SUI).
  3. POST /api/shop/partner-listings/ with source, externalListingId, pickupSsuId, pickupSystemId, typeId, quantity, and priceSui (total SUI for the full quantity; in-game-only currencies are not accepted).
  4. Keep the returned listing id. Same source + externalListingId updates the row.
  5. If the pickup SSU is not Pelusium-authorized, deep-link SSU setup (buildAuthorizePelusiumExtensionTx). Pickup fails until they do.

Check: GET /api/shop/listings/?source=your-app-id&store_id=0x…

Cancel: POST /api/shop/partner-listings/{id}/cancel/ with shop.partnerListing.cancel.

Browser vs server: POST from your backend needs no CORS. POST from the browser requires your site origin to be allowlisted for Pelusium API CORS. There is no partner API key — the wallet proof is the auth.

Do not list units still sitting as exchange receipts. Redeem to hangar first, or a courier cannot pick up.

POST /api/shop/partner-listings/
Content-Type: application/json
{
  "payload": {
    "source": "your-app-id",
    "externalListingId": "listing-123",
    "externalUrl": "https://your.app/listings/123",
    "sellerWallet": "0x…",
    "pickupSsuId": "0x…",
    "pickupSystemId": 300001,
    "pickupSystemName": "Stillness",
    "characterId": "0x…",
    "ownerCapId": "0x…",
    "typeId": 12345,
    "quantity": 10,
    "priceSui": 50,
    "note": "Optional description",
    "requireBond": false,
    "bondOfGoodsPercent": 0,
    "dryRun": false
  },
  "proof": { "address": "0x…", "message": "…", "signature": "…" }
}

Shared SSUs need characterId and ownerCapId. Pelusium verifies the signer owns that SSU.

SDK: upsertPartnerCargoListing, cancelPartnerCargoListing, buildWalletProofMessage.

When a buyer uses Buy & deliver, a verified on-chain create event (matching seller, pickup, cargo, quantity, positive goods escrow) can decrement the partner listing.

Wallet proofs (write endpoints)

POST routes require a wallet proof: the user signs a short UTF-8 message. The proof does not move funds.

Message format (lines must match exactly):

Nebulas Logistics database authorization
Address: 0x…
Action: <action-id>
Issued At: <ISO-8601 timestamp>
Nonce: <unique string>
This signature only authorizes a database write. It cannot move funds.
{
  "proof": {
    "address": "0x…",
    "message": "<full message string>",
    "signature": "<base64 signature>"
  }
}

SDK: buildWalletProofMessage and PELIUSIUM_WALLET_PROOF_ACTIONS.

ActionUsed for
shop.partnerListing.upsertPOST /api/shop/partner-listings/
shop.partnerListing.cancelPOST /api/shop/partner-listings/{id}/cancel/
logistics.intent.createPOST /api/logistics/intents/create-haul/
logistics.intent.confirmPOST /api/logistics/intents/{intentId}/confirm/
logistics.bounty.acceptPOST /api/logistics/flights/{flightNumber}/bounty/
logistics.bounty.selectPOST /api/logistics/flights/{flightNumber}/bounty/select/ (dry-run only)

Proofs expire after about 10 minutes. Nonces are single-use.

Public read API

MethodPathDescription
GET/api/logistics/jobs/?status=open&kind=haul&type_id=Open haul jobs (optional: dropoff, pickup, limit, max_hops, shipper)
GET/api/logistics/prices/?type_id=Implied goods SUI per unit and freight stats (type_id required; cached ~30s)
GET/api/logistics/flights/{PELU-…}/Flight: route, lifecycle, digests
GET/api/logistics/bounty-board/Jobs where both sides opted into bounty visibility
GET/api/shop/listings/?source=&store_id=&type_id=Cargo Well asks. source= is your app id

Jobs with goods_mist > 0 contribute Cargo Well market prints alongside shop listings.

Async haul listing (live)

After a dry run looks good, omit dryRun (or set false). Pelusium computes a route, then the shipper signs on-chain:

  1. POST /api/logistics/intents/create-haul/ — proof logistics.intent.create, wallet, both SSU ids, both solar system ids, typeId, quantity, freightMist, optional goodsMist, optional requireBond / bondOfGoodsPercent, optional source. Default generateRoute: true. HTTP 202 + intentId. Courier bond is 0 unless the seller set a portion and goods are at least 5 SUI. requireBond: true is 110% of goods; bondOfGoodsPercent (1–500) sets a custom portion. Clients cannot invent requiredBondMist, and listing-backed creates use the listing’s portion.
  2. GET /api/logistics/intents/{intentId}/ — poll until ready, failed, or expired.
  3. buildCreateHaulJobTxFromIntent(ready.buildArgs) — user signs and executes.
  4. POST /api/logistics/intents/{intentId}/confirm/ — digest, wallet, proof logistics.intent.confirm. Returns flightNumber (PELU-…).

Ready intents include buildArgs, route (distanceLy, hopCount, hops, summary), and fee inputs.

import {
  buildCreateHaulJobTxFromIntent,
  confirmLogisticsIntent,
  createHaulIntent,
  waitForLogisticsIntent,
} from "@pelusium/sdk"

const BACKEND = "https://api.pelusium.world"

const pending = await createHaulIntent(BACKEND, {
  proof: createProof,
  wallet,
  characterId,
  pickupSsuId,
  dropoffSsuId,
  pickupSystemId: 101,
  dropoffSystemId: 202,
  typeId: 12345,
  quantity: 10,
  freightMist: 10_000_000,
  goodsMist: 50_000_000_000,
  requireBond: false,
  source: "your-app-id",
})

const ready = await waitForLogisticsIntent(BACKEND, pending.intentId)
if (ready.status !== "ready" || !ready.buildArgs) throw new Error(ready.error ?? "Not ready")

const tx = buildCreateHaulJobTxFromIntent(ready.buildArgs)
const { digest } = await signAndExecuteTransaction({ transaction: tx })
const confirmed = await confirmLogisticsIntent(BACKEND, pending.intentId, digest, wallet, confirmProof)
console.log(confirmed.flightNumber)

Direct on-chain create (no intent)

If you already know both SSUs and do not need Pelusium route metadata on the board:

import { STILLNESS_TESTNET, buildCreateHaulJobTx } from "@pelusium/sdk"

const tx = buildCreateHaulJobTx({
  packageId: STILLNESS_TESTNET.packageId,
  protocolConfigId: STILLNESS_TESTNET.protocolConfigId,
  haulConfigId: STILLNESS_TESTNET.haulConfigId,
  freightPaymentMist: 10_000_000n,
  goodsPaymentMist: 0n,
  pickupSsU: "0x…",
  dropoffSsU: "0x…",
  typeId: 12345n,
  quantity: 10,
  slaDurationMs: 86_400_000n,
  requiredBondMist: 0n,
})

Authorize each SSU once with buildAuthorizePelusiumExtensionTx. Typical courier path: buildAcceptHaulJobTx → pickup owner buildApproveHaulPickupTx → buildExecuteHaulPickupTx → buildExecuteHaulDeliverTx.

Grand Exchange (goods + freight books)

Match mode is goods first, freight rests. Settlement is always create_haul_job.

ReadMeaning
GET /api/shop/buy-orders/Goods bids. Matchable rows have dropoffSsU and freightMist. ?wallet= for a shipper’s bids.
GET /api/shop/matches/Pending crosses (bid ≥ ask, dest SSU present). fillQty is the slice.
GET /api/shop/freight-asks/Courier standing asks.
GET /api/logistics/jobs/?status=open&shipper=Open hauls after a goods cross.

Poll GET /api/shop/matches/ and deep-link the buyer to Pelusium Prefill haul. The matcher does not create a haul for you. Partial fills reduce listing and bid quantity; one haul per slice.

Pickup and delivery (same rules as the Pelusium UI)

Partner listings and intents are discovery and routing. They do not move cargo. After a haul exists on-chain, every integrator uses the same Move steps:

StepWho signsSDK helperNotes
Authorize Pelusium on SSUPickup and drop-off owners (once per SSU)buildAuthorizePelusiumExtensionTxRequired before pickup/deliver
Create haul jobShipper (often buyer)buildCreateHaulJobTx or buildCreateHaulJobTxFromIntentLocks freight + optional goods escrow
Accept jobCourierbuildAcceptHaulJobTxLocks bond
Approve pickupPickup SSU OwnerCap holder (seller)buildApproveHaulPickupTxAnti-raid — partner API cannot skip
Execute pickupCourierbuildExecuteHaulPickupTxNeeds freighter character id
Execute deliverCourierbuildExecuteHaulDeliverTxDrop-off SSU must be authorized
Partner listing (HTTP)       →  Cargo Well row only
Buyer Buy & deliver / intent →  create_haul_job (on-chain)
Seller                       →  approve_haul_pickup
Courier                      →  accept → execute_pickup → execute_deliver

Patterns: list via API and deep-link to www.pelusium.world for haul UX; or embed the SDK and prompt each PTB. Hybrid is fine.

Goods escrow pays the goods_seller wallet — the same wallet that should approve pickup when it owns the pickup SSU.

Bounty board

On create intent, set sellerAllowsBounty. After the courier accepts on-chain:

POST /api/logistics/flights/{flightNumber}/bounty/

Proof action logistics.bounty.accept plus the accept transaction digest. The flight appears on /api/logistics/bounty-board/ only when both sides opt in.

On sandbox dry-run flights (PELU-DRY-*), courier opt-in uses proof only (no digest). Hunters select a target with logistics.bounty.select on POST /api/logistics/flights/{flightNumber}/bounty/select/. After simulated delivery, bounties resolve — courier on success, selected hunter on failure.

Fees and limits

  • Protocol fees are charged in Move at create/settle. No separate API billing tier.
  • Writes are rate-limited. Prefer dry run while integrating.
  • The insurance HTTP API is not part of this builder surface.

Prerequisites

  • Sui wallet (proofs). Network SUI only when you confirm a live haul.
  • Pelusium extension authorized on pickup and drop-off SSUs before pickup/deliver.
  • Package / config ids for your network (STILLNESS_TESTNET on Stillness).

Partner app field reference

Map your marketplace or companion app fields to Pelusium API payloads before you call Sell on Pelusium from any storefront.

Your app / APIPelusium fieldNotes
Pickup storage unit idpickupSsuIdOn-chain SSU object id where cargo sits today
Drop-off storage unit iddropoffSsuIdBuyer’s delivery SSU on haul create
Commodity id (integer)typeIdMust match the EVE Frontier item type
App identifiersourceStable string you choose once per integration
Your listing idexternalListingIdUpdates the same Cargo Well row when reused with the same source

Pelusium lists partner asks and haul jobs; it does not run your marketplace’s internal order book or convert non-SUI balances for you.

Example reads (replace placeholders):

GET /api/logistics/jobs/?status=open&dropoff=<storageUnitId>
GET /api/logistics/flights/{PELU-…}/
GET /api/shop/listings/?source=your-app-id

Routing and pathfinding need public solar system ids on both pickup and drop-off when you create a haul intent.

Links