Docs
Dexsta documentation
How labels, bags, DAB, search ads, and operators work — plus HTTP and canister APIs for partners and AI agents.
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.
| Concept | What it means |
|---|---|
| Tokenized Keyword (Lead Label) | 1-of-1 keyword / brand XFT with a bag vault and optional DAB fund token |
| Bag | Vault canister for ICP liquid, DAB supply, staking, oracle |
| DAB | Fixed-supply fund shares of a Tokenized Keyword; price ≈ liquid ÷ remaining |
| Type-5 license | Operator rights on a label (agents, team, super-ops) |
| Type-6 license | Marketplace license — publish weblinks / sell-under a label |
| Search CPC | Pay-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).
2 · Search & CPC ads
On /search, labels are keywords. Licensees publish weblinks (type-6 burns 1 unit per URL). Advertisers bid CPC; rank is highest funded bid first. Each click runs on-chain recordSearchClick.
| Share of CPC | Goes to |
|---|---|
| 33% | Label XFT owner |
| 17% | Label bag via bagOf(labelId) |
| 50% | Platform bag |
Of the 17% bag share: if DAB staking is off → all liquid (creditLiquidFromSale). If staking is on → 5% of CPC to liquid, 12% to stakers. No bag yet → 17% folds into the owner. Manage bids on /search/dashboard.
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).
| Role | Typical perms |
|---|---|
| AI agent | swap · lock · oracles (often no withdraw) |
| Team | withdraw with optional max ICP |
| Super | all 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.
| Route | Auth | Purpose |
|---|---|---|
GET /api/health | Public | Liveness + route map |
GET /api/xft | Public | Single XFT by id or label (getXFT) |
GET /api/xft/user | Public | List XFTs for a principal (getUserXfts) |
GET /api/prices | Public CORS | ICP/USD, crypto, DAB, marketplace marks |
GET /api/media-proxy | Public allowlist | Proxy media ?url= |
POST /api/oracle/advise | Optional secret | Bag HTTPS outcall advice |
POST /api/oracle/subscribe | Optional secret | Oracle subscription helper |
POST /api/sync/* | x-sync-secret | Mirror reindex (ops) |
GET/POST /api/eth/* | Varies | ETH wrap escrow / metadata |
Also see /api (short host notes) and live GET /api/health for the route index.
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
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
# 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)
// 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);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.
# 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)
| Route | Notes |
|---|---|
POST /api/sync/xft | Reindex one XFT into Supabase (x-sync-secret) |
POST /api/sync/owner | Body { owner } — tokensOwnedBy → bulk sync |
POST /api/sync/bag | Bag mirror |
POST /api/sync/listing | Listing mirror |
POST /api/sync/trade | Trade print / market webhook |
POST /api/sync/poller | Cron poller entry |
GET /api/sync/status | Mirror counts (public) |
POST /api/oracle/advise | Bag outcall — optional x-oracle-secret |
GET/POST /api/eth/escrow | Wrap: ownerOf / confirm escrow |
GET /api/eth/metadata | ETH NFT metadata helper |
7 · Canister APIs (search, bag, market)
Search (xft)
| Method | Kind | Use |
|---|---|---|
researchLabel(q) | query | Label meta, bag, CPC depth, click counts, DAB-paid totals |
searchLabelDirectoryRanked(q) | query | Sponsored + organic + agent metrics |
recordSearchClick(bidId) | update | Debit CPC; split owner / bag / platform |
createSearchBid(...) | update | Escrow CPC budget (approve xft first) |
listMySearchBids(owner) | query | Advertiser bids |
export XFT="$(dfx canister id xft)"
dfx canister call "$XFT" researchLabel '("seattle coupons")'
dfx canister call "$XFT" searchLabelDirectoryRanked '("seattle coupons")'Bag DAB
| Method | Use |
|---|---|
getDabPricingSnapshot | liquid, supply, max_supply, price_e8s |
getDabStakingConfig | enabled, fee_bps, total_staked, transfer_fee_e8s |
quoteDabBuyDetailed / quoteDabSellDetailed | Slippage quotes |
buyDabTokens | ICP → shares (ICRC-2 approve bag) |
redeemDabTokens | Shares → liquid ICP |
stakeDabTokens / unstakeDabTokens | If staking enabled |
Operator
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
sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)" # or: brew install dfinity/dfx/dfx dfx --version
2. Create identity & show principal
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.
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
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)
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);