Docs

Dexsta documentation

How labels, bags, DAB, search ads, and operators work — plus HTTP and canister APIs for partners and AI agents.

ProductHTTP APICanister C2CAgents

What is Dexsta?

Dexsta is an Internet Computer marketplace for XFTs (extended fungible/non-fungible tokens): Tokenized Keywords (Lead Labels) with vault bags, DAB fund shares, licenses, media, and on-chain search ads. The wallet home is / when you connect Internet Identity.

ConceptWhat it means
Tokenized Keyword (Lead Label)1-of-1 keyword / brand XFT with a bag vault and optional DAB fund token
BagVault canister for ICP liquid, DAB supply, staking, oracle
DABFixed-supply fund shares of a Tokenized Keyword; price ≈ liquid ÷ remaining
Type-5 licenseOperator rights on a label (agents, team, super-ops)
Type-6 licenseMarketplace license — publish weblinks / sell-under a label
Search CPCPay-per-click bids on /search; a cut credits the label bag

1 · Labels, bags & DAB

Mint a Tokenized Keyword (Lead Label) at Create Lead. You set fixed DAB max supply at launch. Fans need a license pass (media/generative registered on the bag) to buy DAB; redeeming DAB for ICP never requires a pass.

First-sale liquid:on a pass XFT, first-sale DAB % routes a cut of the creator's first market sale into the Lead bag liquid. Secondary sales: 0%.

Staking: label owner/operator can enable DAB staking (1–500 bps of trade notional). Rewards vest over ~1 year. Non-trade DAB transfers charge a flat ICP fee (default 0.01 ICP) to the staking fee wallet.

Label registration: named labels expire. Renew extends the same XFT and bag. After burn, reclaim by minting the same text (new XFT id, bag rebind).

3 · Operators (type-5)

Label owners grant operators on Operators. A type-5 license XFT linked to the label is transferred to the operator principal, then registered with permissions (swap, lock, oracles, withdraw, super).

RoleTypical perms
AI agentswap · lock · oracles (often no withdraw)
Teamwithdraw with optional max ICP
Superall bag ops + manage other operators

Step-by-step with dfx (identity, principal, license transfer, bag calls): see Agent wallet below.

4 · HTTP API overview

Partner-facing HTTPS lives on the Node Next host (npm run dev / next start). Static IC asset deploys do not serve /api/*. Canister SoT is always authoritative; HTTP is for browsers and off-chain partners.

RouteAuthPurpose
GET /api/healthPublicLiveness + route map
GET /api/xftPublicSingle XFT by id or label (getXFT)
GET /api/xft/userPublicList XFTs for a principal (getUserXfts)
GET /api/pricesPublic CORSICP/USD, crypto, DAB, marketplace marks
GET /api/media-proxyPublic allowlistProxy media ?url=
POST /api/oracle/adviseOptional secretBag HTTPS outcall advice
POST /api/oracle/subscribeOptional secretOracle subscription helper
POST /api/sync/*x-sync-secretMirror reindex (ops)
GET/POST /api/eth/*VariesETH wrap escrow / metadata

Also see /api (short host notes) and live GET /api/health for the route index.

bashSmoke
curl -sS "$HOST/api/health" | jq .
curl -sS "$HOST/api/xft?id=1" | jq .
curl -sS "$HOST/api/xft?label=usa%20politics" | jq .
curl -sS "$HOST/api/xft/user?owner=$PRINCIPAL" | jq .
curl -sS "$HOST/api/prices?include=crypto&symbols=ICP" | jq .

5 · getXFT & getUserXfts

These are the core read APIs partners use to resolve ownership (e.g. Betable categories) and portfolios.

Single XFT — HTTPS

httpGET /api/xft
GET /api/xft?id=42
GET /api/xft?label=usa%20politics
GET /api/xft?label=usapolitics
GET /api/xft?contract=<xft-canister>&id=42

# Response (success)
{
  "success": true,
  "exists": true,
  "contract": "<xft-principal>",
  "xftId": 42,
  "label": "usapolitics",
  "owner": "<current-owner-principal>",
  "creator": "<mint-time-creator>",
  "bag": "<bag-principal-or-null>",
  "settings": {
    "linkedTo": 0,
    "xftType": 1,
    "quantity": 1,
    "transferable": true,
    "labelExpire": 0,
    "labelSplitBps": 0,
    ...
  },
  "media": [...],
  "game_asset": false,
  "imageUri": "...",
  "metadataUri": "..."
}

User portfolio — HTTPS

httpGET /api/xft/user
# List token refs owned by a principal (getUserXfts)
GET /api/xft/user?owner=<principal>
GET /api/xft/user?principal=<principal>&limit=40

# Game-asset XFTs only (type-8 image/audio inventory flagged at mint; qty 1+)
GET /api/xft/user?owner=<principal>&game_assets=1

# Hydrate each with getXFT (slower)
GET /api/xft/user?owner=<principal>&detail=1&limit=20

# Response (without detail)
{
  "success": true,
  "owner": "<principal>",
  "count": 3,
  "truncated": false,
  "xfts": [
    { "contract": "<xft>", "xftId": 12, "href": "/xft?contract=...&id=12" }
  ],
  "source": "getUserXfts"
}

Canister C2C (preferred on IC)

motokoxft canister
// Full snapshot — owner is CURRENT owner (not only creator)
let data = await Xft.getXFT(xftContract, tokenId);
// data.owner : ?Principal
// data.gameAsset : Bool  (type-8 game inventory asset)
// data.mediaCategory : Nat  (settings[17]; 0=unset; 1–49 audio; 100–149 video)
// data.settings, data.addresses.bag, data.media, data.exists

// Portfolio: (contract, tokenId) pairs
let owned = await Xft.getUserXfts(userPrincipal);
// [(Principal, Nat), ...]

// Game assets only (follows transfers via ownerXftIndex)
let gameAssets = await Xft.getUserGameAssetXfts(userPrincipal);

// Fallback on some builds
let ids = await Xft.tokensOwnedBy(userPrincipal); // [Nat] on self canister

// Label helpers
let ?id = await Xft.getLabelIdByText("usa politics");
let ?text = await Xft.getLabelTextById(id);
let ?bag = await Xft.bagOf(id);
bashdfx examples
export XFT="$(dfx canister id xft)"

dfx canister call "$XFT" getXFT "(
  principal \"$XFT\",
  1 : nat
)"

dfx canister call "$XFT" getUserXfts "(
  principal \"$OWNER\"
)"

dfx canister call "$XFT" getUserGameAssetXfts "(
  principal \"$OWNER\"
)"

dfx canister call "$XFT" getLabelIdByText '("seattle coupons")'
dfx canister call "$XFT" bagOf "(1 : nat)"

6 · Prices API

Spot marks for ICP, crypto (XRC), DAB bags, and marketplace listings. Public CORS.

bashGET /api/prices
# Crypto / ICP
GET /api/prices?include=crypto&symbols=ICP,BTC,ETH

# DAB bags
GET /api/prices?include=dab&bags=<bag1>,<bag2>

# Marketplace listings sample
GET /api/prices?include=marketplace&limit=48

# One XFT listing context
GET /api/prices?xft_contract=<p>&xft_id=12

# Convert e8s
GET /api/prices?convert=usd_to_icp&amount_e8s=100000000
GET /api/prices?convert=icp_to_usd&amount_e8s=100000000

# Everything (heavier)
GET /api/prices?include=all

Sync, oracle & ETH (ops)

RouteNotes
POST /api/sync/xftReindex one XFT into Supabase (x-sync-secret)
POST /api/sync/ownerBody { owner } — tokensOwnedBy → bulk sync
POST /api/sync/bagBag mirror
POST /api/sync/listingListing mirror
POST /api/sync/tradeTrade print / market webhook
POST /api/sync/pollerCron poller entry
GET /api/sync/statusMirror counts (public)
POST /api/oracle/adviseBag outcall — optional x-oracle-secret
GET/POST /api/eth/escrowWrap: ownerOf / confirm escrow
GET /api/eth/metadataETH NFT metadata helper

7 · Canister APIs (search, bag, market)

Search (xft)

MethodKindUse
researchLabel(q)queryLabel meta, bag, CPC depth, click counts, DAB-paid totals
searchLabelDirectoryRanked(q)querySponsored + organic + agent metrics
recordSearchClick(bidId)updateDebit CPC; split owner / bag / platform
createSearchBid(...)updateEscrow CPC budget (approve xft first)
listMySearchBids(owner)queryAdvertiser bids
bashSearch research
export XFT="$(dfx canister id xft)"
dfx canister call "$XFT" researchLabel '("seattle coupons")'
dfx canister call "$XFT" searchLabelDirectoryRanked '("seattle coupons")'

Bag DAB

MethodUse
getDabPricingSnapshotliquid, supply, max_supply, price_e8s
getDabStakingConfigenabled, fee_bps, total_staked, transfer_fee_e8s
quoteDabBuyDetailed / quoteDabSellDetailedSlippage quotes
buyDabTokensICP → shares (ICRC-2 approve bag)
redeemDabTokensShares → liquid ICP
stakeDabTokens / unstakeDabTokensIf staking enabled

Operator

textoperator canister
setOperatorWithLicense(operator, xftContract, labelId, licenseId, isSuper, perms)
isOperatorActive(operator, labelId, xftContract)
// XFT: isValidOperatorLicense(holder, licenseId, labelId)

8 · Agent wallet (dfx + type-5)

An AI agent is a dfx identity (principal) that holds a type-5 operator license and is registered on the operator canister — then it can call the label bag and search APIs with signed updates.

1. Install dfx

bash
sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"
# or: brew install dfinity/dfx/dfx
dfx --version

2. Create identity & show principal

bash
dfx identity new dexsta_agent
dfx identity use dexsta_agent
dfx identity get-principal     # paste into Operators UI
dfx ledger account-id          # ICP ledger account hex
dfx ledger balance --network local

# List all local identities
for name in $(dfx identity list); do
  printf "%-28s %s\n" "$name" "$(dfx identity get-principal --identity "$name")"
done

3. Grant type-5 operator (owner → agent)

UI: Lead Label → Operators → principal + type-5 license id → permissions. Or CLI: transfer license, then setOperatorWithLicense.

bash
export XFT="$(dfx canister id xft)"
export OP="$(dfx canister id operator)"
export AGENT="$(dfx identity get-principal --identity dexsta_agent)"
export LABEL_ID=1
export LICENSE_ID=42

# Owner transfers type-5 license to agent, then:
dfx canister call "$OP" setOperatorWithLicense "(
  principal \"$AGENT\",
  principal \"$XFT\",
  $LABEL_ID : nat,
  $LICENSE_ID : nat,
  false,
  record {
    can_withdraw = false;
    can_swap = true;
    can_lock = true;
    can_set_oracles = true;
    max_withdraw_amount = null;
    expires_at = null;
  }
)"

dfx canister call "$XFT" bagOf "($LABEL_ID : nat)"
# → export BAG=...

4. Swap / trade as agent

bash
dfx identity use dexsta_agent
export BAG="<from-bagOf>"
export AMOUNT_E8S=10000000   # 0.1 ICP
export ICP_LEDGER="$(dfx canister id icp_ledger 2>/dev/null || echo ryjl3-tyaaa-aaaaa-aaaba-cai)"

dfx canister call "$BAG" getDabPricingSnapshot
dfx canister call "$BAG" quoteDabBuyDetailed "($AMOUNT_E8S : nat)"

# Approve bag, then buy
dfx canister call "$ICP_LEDGER" icrc2_approve "(
  record {
    fee = null; memo = null; from_subaccount = null;
    created_at_time = null; expected_allowance = null; expires_at = null;
    amount = $((AMOUNT_E8S + 10000)) : nat;
    spender = record { owner = principal \"$BAG\"; subaccount = null };
  }
)"

dfx canister call "$BAG" buyDabTokens "(
  principal \"$(dfx identity get-principal)\",
  $AMOUNT_E8S : nat,
  principal \"$(dfx canister id xft)\",
  0 : nat,
  0 : nat
)"

dfx canister call "$BAG" redeemDabTokens "(
  principal \"$(dfx identity get-principal)\",
  50 : nat,
  0 : nat
)"

5. TypeScript (app lib)

typescript
import { getAgentSearchSnapshot } from "@/lib/search-ads";
import { listOwnedXfts } from "@/lib/portfolio";

// Keyword demand + bag DAB signals
const snap = await getAgentSearchSnapshot("seattle coupons");
// snap.research.bag, totalClicks, totalDabPaidE8s, buySignalScore, …

// Portfolio (canister getUserXfts + hydrate)
const { cards } = await listOwnedXfts(principalText);