01 · OverviewWhat you'll build
Every exchange, launchpad and wallet that lists tokens faces the same question: is this one safe to put in front of users? Answering it well means buying a contract audit, a holder scan, a liquidity check, sanctions screening, wash-trading and bot detection, then deciding what to do when they disagree. Skein does that in one call. It buys each check from independent miners on the Telegraph network, fuses them under a published policy, and returns one verdict: APPROVE, REJECT, ESCALATE or ABSTAIN. Every input carries a receipt, and the whole decision carries a hash anyone can recompute.
In this tutorial you will:
- 01
Get a free quote with curl
See what a verdict buys and costs, without an account. - 02
Buy, stream and verify a verdict from TypeScript
With@skeinlabs/sdk, spend caps, typed errors and idempotency. - 03
Pay per call through Telegraph
No account at all: each verdict settles from your own escrow. - 04
Hand the same tools to an AI agent
With@skeinlabs/mcpin Claude Code, Claude Desktop or Cursor. - 05
Ship a listing gate
A script that decides list, block, review or retry, and fails CI on a block.
What you need
- Node.js 20 or later. The SDK also runs on Bun, Deno and edge runtimes: anything with
fetch. - For paid verdicts, one of: a partner key (free during the Telegraph hackathon; request one), or a funded Telegraph burner wallet, set up in section 09.
- Quotes, verdict pages, event streams and verification are free and need neither.
02 · ConceptsHow a verdict works
A verdict is made in four steps, and each one leaves something you can check afterwards.
- 01
Plan
The policy (for exampledex-list@0.1.0) names the checks it needs. Skein maps each to a Telegraph intent and prices it from the live miner catalogue. - 02
Buy
Each check is one paid Telegraph ask to an independent miner. Each answer comes back with a receipt hash and a signal hash. - 03
Fuse
The engine turns each raw answer into a finding, applies the policy's blockers, weights and conflict rules, and decides. Missing or failed required checks lead to ABSTAIN rather than a guess. - 04
Seal
The decision, the policy version, the engine version and a Merkle root over every receipt (the bill of materials) are hashed into the verdict hash.
| Verdict | Meaning | What your code should do |
|---|---|---|
| APPROVE | Every required check answered and nothing crossed a blocker. | Proceed (list, pay out, trade). |
| REJECT | A blocker fired: in dex-list, a failed sanctions screen or a contract audit failing at high severity. | Stop, and show the reasons. |
| ESCALATE | Evidence is mixed or sources conflict. | Send it to a human reviewer. |
| ABSTAIN | Not enough evidence arrived to decide. Charged at upstream cost only. | Retry later; never treat it as approval. |
Three numbers qualify the verdict. riskBps is how risky the token looks, from 0 to 10,000 basis points. confidenceBps is how sure the engine is, given what arrived. coverage is the share of the policy's checks that answered. A low-risk verdict with 40% coverage is weaker than it looks, and the policy accounts for that.
03 · First callYour first call, with curl
Start with a quote. It is free and needs no key: it tells you which checks a verdict would buy for this token, from which Telegraph intents, at what upstream price, and what Skein charges. Here it is for WETH on Base:
curl "https://api.skeinlabs.site/v1/quote?chain=8453&token=0x4200000000000000000000000000000000000006"
{
"subject": { "chainId": 8453, "token": "0x4200000000000000000000000000000000000006" },
"policy": "dex-list@0.1.0",
"priceUsdc": 0.25,
"pricing": "standard; ABSTAIN is charged at upstream cost only",
"ok": true,
"upstreamUsdc": 0.06,
"checks": [
{ "check": "contract_audit", "intent": "SMART_CONTRACT_AUDIT_GENERAL", "quoteUsdc": 0.01 },
{ "check": "holder_count", "intent": "TOKEN_HOLDER_COUNT_GENERAL", "quoteUsdc": 0.01 },
{ "check": "liquidity_depth", "intent": "LIQUIDITY_DEPTH_VERIFY_GENERAL", "quoteUsdc": 0.01 },
{ "check": "sanctions", "intent": "SANCTIONS_SCREENING_MATCH_GENERAL", "quoteUsdc": 0.01 },
{ "check": "social_bots", "intent": "SOCIAL_BOT_DETECTION_GENERAL", "quoteUsdc": 0.01 },
{ "check": "wash_trading", "intent": "WASH_TRADING_DETECTION_GENERAL", "quoteUsdc": 0.01 }
]
}When a verdict would be refused (a required intent has no live miner, or the live floors are above your budget), the quote says so with "ok": false and a refusal code, before any money moves. A paid verdict over plain HTTP looks like this. You'll rarely write it by hand, because the SDK does it for you:
curl -X POST https://api.skeinlabs.site/v1/verdicts \
-H "Authorization: Bearer $SKEIN_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"subject": { "chainId": 8453, "token": "0x4200000000000000000000000000000000000006" },
"policy": "dex-list@0.1.0",
"mode": "async"
}'
# → 202 { "id": "vrd_…", "status": "running" }
curl https://api.skeinlabs.site/v1/verdicts/vrd_… # the verdict, once sealed
curl -X POST https://api.skeinlabs.site/v1/verify \
-H "Content-Type: application/json" -d '{ "id": "vrd_…" }'The Idempotency-Key is required. Send the same key with the same body again and you get the original verdict back, marked replayed, and you are not charged twice. Send it with a different body and you get 409 IDEMPOTENCY_CONFLICT.
04 · InstallInstall the SDK
@skeinlabs/sdk is a small TypeScript client with no runtime dependencies. It wraps every endpoint and adds typed errors, retries that keep the idempotency key, and a spend cap checked before any payment.
mkdir listing-gate && cd listing-gate npm init -y && npm pkg set type=module npm install @skeinlabs/sdk npm install -D tsx typescript @types/node
// quote.ts: run with npx tsx quote.ts
import { Skein } from "@skeinlabs/sdk";
const skein = new Skein({ apiUrl: "https://api.skeinlabs.site" });
const q = await skein.quote({
subject: { chainId: 8453, token: "0x4200000000000000000000000000000000000006" }, // WETH on Base
policy: "dex-list@0.1.0",
});
if (q.ok) console.log(`${q.priceUsdc} USDC buys ${q.checks?.length} checks`);
else console.log("would be refused:", q.refusal?.code, q.refusal?.detail);05 · VerdictBuy a verdict
Add a payer and call verdict(). With maxUsdcPerCall set, the SDK fetches a quote first and throws a SpendCapError if the price is above your cap, so nothing is paid. Then it submits the verdict, follows its event stream until it is sealed, and returns the full object.
// verdict.ts: SKEIN_API_KEY=sk_part_… npx tsx verdict.ts
import { Skein, SkeinError, SpendCapError } from "@skeinlabs/sdk";
const skein = new Skein({
apiUrl: "https://api.skeinlabs.site",
payer: { kind: "apiKey", key: process.env.SKEIN_API_KEY! },
maxUsdcPerCall: 0.3, // quoted first; refused before paying above this
});
try {
const v = await skein.verdict({
subject: { chainId: 8453, token: "0x4200000000000000000000000000000000000006" },
policy: "dex-list@0.1.0",
});
console.log(v.verdict, v.riskBps, "bps");
console.log(v.summary);
for (const r of v.reasons) console.log("-", r.detail);
console.log("open risks:", v.openRisks);
console.log("cost:", v.cost.priceUsdc, "USDC, upstream", v.cost.upstreamUsdc);
console.log("page:", "https://skeinlabs.site/v/" + v.id);
} catch (e) {
if (e instanceof SpendCapError) console.log("too expensive:", e.priceUsdc, "USDC");
else if (e instanceof SkeinError) console.log(e.code, e.message);
else throw e;
}What comes back
interface VerdictObject {
id: string; // "vrd_…", public: anyone can GET, verify and audit it
verdict: "APPROVE" | "REJECT" | "ESCALATE" | "ABSTAIN";
riskBps: number; // 0–10000: how risky, in basis points
confidenceBps: number; // 0–10000: how sure, given the evidence that arrived
coverage: number; // 0–1: share of the policy's checks that answered
summary: string; // one line a human can read
reasons: { code: string; check?: string; detail: string }[];
openRisks: string[]; // what was not settled (a check that failed, a conflict)
conflicts: string[]; // where two sources disagreed
findings: Finding[]; // one per check: status, severity, value, source, receipt
cost: { upstreamUsdc: number; priceUsdc: number; currency: "USDC" };
bom: { root: string; items: number }; // Merkle root over every paid input's receipt
policy: { id: string; version: string };
engineVersion: string;
verdictHash: string; // hash over the whole decision: recompute it with verify()
network: string; // "testnet" today; "simulated" means canned answers
cached?: boolean; // served from cache: no new purchases
replayed?: boolean; // same Idempotency-Key as before: not charged again
payment: { rail: "apiKey" } | { rail: "telegraph"; minerId; amountUsdc; receiptHash; signalHash };
}Here is a real one. On 4 November, before Skein's own upstream wallet was funded, its purchases failed, and it did the right thing: it abstained, named every required check that was missing, and charged nothing.
{
"id": "vrd_mw1xqc4d3k4t22320e31",
"verdict": "ABSTAIN",
"summary": "Abstained: required check contract_audit is ERROR (+3 more).",
"reasons": [
{ "code": "MISSING_REQUIRED_CHECK", "check": "contract_audit", "detail": "required check contract_audit is ERROR" },
…
],
"cost": { "upstreamUsdc": 0, "priceUsdc": 0, "pricingRule": "abstain_upstream_only", "currency": "USDC" },
"bom": { "root": "0x0000…0000", "items": 0 },
"verdictHash": "0x8f1ac48e5260f1c43b36942a1df39af128c5f747f7ac85564c253095437ec923",
"network": "testnet",
"durationMs": 2379
}06 · StreamWatch it being made
A verdict takes seconds, because each check is a separate paid ask. Pass onEvent to see each step as it happens. The same events drive the thread graph on every verdict page.
| Event | When | Data |
|---|---|---|
plan | Before any purchase | The checks and intents to buy, and their quotes |
purchase | Each paid ask settles | Check, miner, amount in micro-USDC, receipt hash, or an error |
finding | Each answer is parsed | Check, status (PASS, WARN, FAIL, UNKNOWN, UNAVAILABLE, ERROR), severity, value, notes |
decision | The engine decides | Verdict, risk, and the verdict hash |
done | Sealed and stored | The verdict id |
refused | Refused before spending | A refusal code and detail |
const v = await skein.verdict({
subject,
onEvent: (e) => {
switch (e.type) {
case "plan": console.log("buying", e.data.purchases?.map((p) => p.check)); break;
case "purchase": console.log("paid", e.data.check, e.data.amountUusdc / 1e6, "USDC", e.data.receiptHash); break;
case "finding": console.log(e.data.check, e.data.status, e.data.notes.join("; ")); break;
case "decision": console.log("decided", e.data.decision.verdict); break;
}
},
});For queue workers and serverless functions, split the call in two. submit() returns as soon as the verdict has an id; wait() collects it later, following the stream and falling back to polling if the connection drops.
// Start now, collect later: useful in a queue worker or a serverless function.
const started = await skein.submit({ subject, idempotencyKey: jobId });
if ("status" in started && started.status === "running") {
await queue.later({ verdictId: started.id }); // returns in milliseconds
}
// …in another invocation:
const v = await skein.wait(verdictId, { timeoutMs: 60_000 });
// Or follow any verdict's events yourself, replayed from the start:
for await (const e of skein.stream(verdictId)) console.log(e.type);07 · VerifyVerify it
A verdict should not have to be trusted. verify() asks Skein to recompute it from the raw answers it paid for: re-run every adapter, re-apply the published policy, rebuild the receipts' Merkle root, and compare the hashes. It is free, and it works on anyone's verdict, not only yours.
const report = await skein.verify(v.id);
// {
// ok: true,
// checks: {
// adaptersRerun: true, // each raw paid answer re-parsed into the same finding
// policyRerun: true, // the policy file re-applied to those findings
// policyFile: true, // the policy text matches its published sha256
// engineVersion: true, // the engine that sealed it is the one that re-ran it
// decisionMatches: true, // same verdict, risk and confidence
// bomRootRebuilt: true, // the receipts' Merkle root rebuilt from the lines
// receiptsMatch: true // every line's receipt is the one Telegraph returned
// },
// verdictHash: { stored: "0x…", recomputed: "0x…" },
// problems: []
// }
if (!report.ok) throw new Error("verdict did not verify: " + report.problems.join("; "));
const bom = await skein.bom(v.id); // the bill of materials
for (const line of bom.lines) {
console.log(line.check, line.minerName, line.amountUsdc, "USDC", line.receiptHash);
}Every verdict response also carries X-Skein-Verdict-Hash and X-Skein-Bom-Root headers, and the verify page does the same check in a browser. Every receipt hash in the bill of materials is the one the Telegraph network returned for that purchase, so an auditor can trace each input to a settled payment.
08 · ProductionProduction patterns
Cap spend in three places
maxUsdcPerCall refuses one expensive verdict. Your own running total, as in the listing gate below, caps a batch. And each partner key carries a daily cap on Skein's side, so a runaway loop stops there too. The Telegraph client adds a fourth: it will not sign a payment challenge above its own maxUusdcPerCall.
Make retries safe
Derive the idempotency key from what you are deciding, not from the attempt. For example, one key per token per day. A crash, a retry or a re-run then returns the same verdict without paying again. The SDK only retries a POST when it carries a key, and it never sleeps longer than maxRetryWaitMs in total: a Retry-After until midnight throws at once instead of hanging your worker.
const skein = new Skein({
apiUrl: "https://api.skeinlabs.site",
payer: { kind: "apiKey", key },
maxUsdcPerCall: 0.3, // checked against a fresh quote before any payment
retries: 2, // network errors, 429 and retryable 5xx (POSTs only with an idempotency key)
maxRetryWaitMs: 10_000, // never sleep longer than this in total; a longer Retry-After throws
requestTimeoutMs: 15_000, // per HTTP request
timeoutMs: 120_000, // how long verdict() waits for the seal
});Handle every outcome
All errors are SkeinError, with the API's code. A SpendCapError means nothing was paid. A SkeinError with a payment field means money moved (or may have) without a verdict, which can happen on the Telegraph rail, so count it against your caps.
import { SkeinError, SpendCapError } from "@skeinlabs/sdk";
try {
return await skein.verdict({ subject, idempotencyKey });
} catch (e) {
if (e instanceof SpendCapError) return hold("price above cap", e.priceUsdc); // nothing paid
if (!(e instanceof SkeinError)) throw e;
if (e.payment) ledger.add(e.payment.amountUsdc); // paid, but no verdict: count it
switch (e.code) {
case "BUDGET_TOO_LOW": // live floors above your budget
case "UPSTREAM_UNAVAILABLE": // a required check has no live miner
case "DAILY_CAP_REACHED": // Skein's own cap for the day
return retryLater(e.code);
case "RATE_LIMITED": // the SDK already waited and retried
case "TIMEOUT":
return retryLater(e.code, { sameKey: idempotencyKey });
default:
throw e; // INVALID_SUBJECT, UNAUTHORIZED, …
}
}| Code | HTTP | Meaning |
|---|---|---|
INVALID_SUBJECT | 400 | Unknown chain, or a malformed token address |
UNKNOWN_POLICY | 400 | No published policy by that id and version |
PAYMENT_REQUIRED | 402 | No partner key sent: send one, or pay through Telegraph |
UNAUTHORIZED | 401 | Unknown or revoked partner key |
IDEMPOTENCY_CONFLICT | 409 | The key was used before with a different body |
BUDGET_TOO_LOW | 422 | Live floors exceed your budget; refused before any spend |
RATE_LIMITED | 429 | Slow down; Retry-After says how long |
UPSTREAM_UNAVAILABLE | 503 | A required intent has no live miner right now |
DAILY_CAP_REACHED | 503 | Skein's own spend cap for the day is used up |
TIMEOUT | 504 | Upstream answers did not arrive in time; call wait(id) again |
SPEND_CAP | SDK | The quote is above your maxUsdcPerCall; nothing was paid |
Cache what the network caches
A verdict for the same token and policy within ten minutes is served from cache, marked cached, and buys nothing upstream. That is fine for a listing review, and it is why the listing gate checks cached and replayed before adding to its spend.
09 · TelegraphPay per call, with no account
Skein is itself a miner on Telegraph, so any Telegraph app can buy a verdict with one paid ask, settled from its own escrow. There is no key to request: you need a wallet with test USDC in the Telegraph escrow on Sepolia.
Fund a burner
# 1. A fresh burner wallet (Foundry's cast; or any wallet you only use for this) cast wallet new export KEY=0x… # the burner's private key: never a wallet that holds real funds export ME=0x… # its address export RPC=https://ethereum-sepolia-rpc.publicnode.com export USDC=0xcc194e1a843B658526D0D5C431f670028582Ae17 # Telegraph test USDC export DIAMOND=0x3624EB012B5E998BB57B6bE6C9Cfc9c4bc5eb99b # Telegraph escrow # 2. Sepolia ETH for gas: any public Sepolia faucet, sent to $ME # 3. Mint 10 test USDC (6 decimals): anyone can mint the test token cast send $USDC "mint(address,uint256)" $ME 10000000 --rpc-url $RPC --private-key $KEY # 4. Deposit 5 USDC into escrow: paid asks settle from here cast send $USDC "approve(address,uint256)" $DIAMOND 5000000 --rpc-url $RPC --private-key $KEY cast send $DIAMOND "depositUSDC(uint256)" 5000000 --rpc-url $RPC --private-key $KEY # 5. Check the escrow balance (micro-USDC) cast call $DIAMOND "effectiveBalance(address)(int256)" $ME --rpc-url $RPC
Pay for a verdict
npm install viem # the Telegraph entry signs with it
// telegraph.ts: SKEIN_PAYER_KEY=0x… npx tsx telegraph.ts
import { Skein } from "@skeinlabs/sdk";
import { TelegraphClient, TESTNET, viemSigner } from "@skeinlabs/sdk/telegraph";
const client = new TelegraphClient({
network: TESTNET, // Sepolia escrow, Telegraph dev node
signer: viemSigner(process.env.SKEIN_PAYER_KEY as `0x${string}`),
maxUusdcPerCall: 300_000, // 0.30 USDC: no challenge is signed above this
});
const skein = new Skein({
apiUrl: "https://api.skeinlabs.site", // only for reading the sealed verdict back
payer: {
kind: "telegraph",
client,
minerId: process.env.SKEIN_MINER_ID, // pin Skein's miner (see /status)
},
});
const v = await skein.verdict({ subject: { chainId: 1, token: "0x…" } });
console.log(v.verdict, v.payment);
// { rail: "telegraph", minerId: "…", amountUsdc: …, receiptHash: "0x…", signalHash: "0x…" }The client reads the network's 402 challenge and checks every field of the message it is asked to sign against the challenge itself. It refuses anything above its cap, signs with EIP-191, and never retries a call it has already paid for. The result's payment carries the receipt hash of your own purchase, and the verdict's bill of materials carries the receipts of every purchase Skein made upstream.
10 · MCPGive it to an agent
@skeinlabs/mcp is a Model Context Protocol server that gives any MCP client three tools: skein_quote (free), skein_verdict (paid) and skein_verify (free). It pays inside, with a per-call cap and a per-session cap, so an agent cannot overspend however it loops. And it returns each verdict as a few lines the model can act on, plus the full object as structured content.
Claude Code
claude mcp add skein --env SKEIN_API_KEY=sk_part_… -- npx -y @skeinlabs/mcp
Claude Desktop, Cursor and others
Add the server to the client's MCP config. That's claude_desktop_config.json for Claude Desktop (Settings → Developer → Edit Config), ~/.cursor/mcp.json or a project's .cursor/mcp.json for Cursor, and the same shape for most other clients. Then restart the client.
{
"mcpServers": {
"skein": {
"command": "npx",
"args": ["-y", "@skeinlabs/mcp"],
"env": {
"SKEIN_API_KEY": "sk_part_…",
"SKEIN_MAX_USDC_PER_CALL": "0.30",
"SKEIN_SESSION_CAP_USDC": "2"
}
}
}
}| Variable | Default | Meaning |
|---|---|---|
SKEIN_API_KEY | A partner key | |
SKEIN_PAYER_KEY | Instead of a key: a funded Telegraph burner | |
SKEIN_MINER_ID | Pin Skein's miner on Telegraph | |
SKEIN_MAX_USDC_PER_CALL | 0.30 | No verdict is bought above this |
SKEIN_SESSION_CAP_USDC | 2 | What one server process may spend in total |
SKEIN_API_URL | api.skeinlabs.site |
What it looks like
You: Before we add 0x4200000000000000000000000000000000000006 on Base to the
launchpad, check it with Skein. Tell me the cost first.
Agent → skein_quote { chainId: "base", token: "0x4200…0006" }
← dex-list@0.1.0: 0.25 USDC per verdict; buys 6 checks upstream for about 0.06 USDC
Agent → skein_verdict { chainId: "base", token: "0x4200…0006" }
← <VERDICT> · risk <n> bps · confidence <n>% · coverage <n>%
<summary, reasons, open risks, cost>
Verdict hash 0x…. Verify: https://skeinlabs.site/v/vrd_…
Agent → skein_verify { id: "vrd_…" }
← VERIFIED: vrd_… ✓ adaptersRerun ✓ policyRerun ✓ decisionMatches …11 · Build itBuild the listing gate
Now put it together. The gate takes a list of tokens, checks each one within a per-run budget, verifies every verdict, and maps it to a decision. It writes decisions.json and exits with status 1 if anything is blocked, so a CI job or a listing pipeline can stop on it. The whole program is about a hundred lines; read it on GitHub. Here is its core:
const DECISION = { APPROVE: "list", REJECT: "block", ESCALATE: "review", ABSTAIN: "retry" } as const;
for (const subject of subjects) {
// 1. Free: what it would cost, or why it would be refused, before any money moves.
const quote = await skein.quote({ subject, policy });
if (!quote.ok) { refuse(subject, quote.refusal); continue; }
if (spent + quote.priceUsdc > runCapUsdc) { refuse(subject, "run cap"); continue; }
// 2. Buy it. One idempotency key per token per day: re-running today is free.
const v = await skein.verdict({
subject, policy, quote,
idempotencyKey: `listing-gate:${subject.chainId}:${subject.token.toLowerCase()}:${day}`,
});
spent += v.cached || v.replayed ? 0 : v.cost.priceUsdc;
// 3. Don't take it on trust: recompute it.
const report = await skein.verify(v.id);
// 4. Act. A verdict that fails verification goes to a human, never to "list".
decide(subject, report.ok ? DECISION[v.verdict] : "review", v);
}
process.exit(rows.some((r) => r.decision === "block") ? 1 : 0); // CI fails on a blockgit clone -b ccr-4e0a77db-k2xyoy https://github.com/linoxbt/skein.git && cd skein/examples/listing-gate
npm install
SKEIN_API_KEY=sk_part_… npm run gate -- \
base:0x4200000000000000000000000000000000000006 \
ethereum:0xdAC17F958D2ee523a2206206994597C13D831ec7
# one block per token: decision, chain, token, verdict and risk, then why and the verdict page
<DECISION> 8453 0x4200…0006 <VERDICT> <risk> bps
<summary>
https://skeinlabs.site/v/vrd_…
<DECISION> 1 0xdAC1…1ec7 <VERDICT> <risk> bps
<summary>
https://skeinlabs.site/v/vrd_…
2 tokens · spent <total> USDC (cap 2)Run it on every listing request
Keep the tokens you list in a file, and let a pull request that adds one run the gate. A block fails the check. A review shows up in the uploaded decisions, with a link to each verdict page for whoever approves the PR.
# .github/workflows/listing-gate.yml: check every token added to tokens.txt
on:
pull_request:
paths: ["tokens.txt"]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npx tsx gate.ts $(cat tokens.txt)
env:
SKEIN_API_KEY: ${{ secrets.SKEIN_API_KEY }}
SKEIN_RUN_CAP_USDC: "5"
- uses: actions/upload-artifact@v4
if: always()
with: { name: decisions, path: decisions.json }12 · TroubleshootingTroubleshooting
| Symptom | Cause and fix |
|---|---|
NO_PAYER | The client has no payer. Quotes and verification work without one; verdicts need a key or a Telegraph client. |
PAYMENT_REQUIRED | No Authorization: Bearer header. Check that the key is in your environment. |
UNAUTHORIZED | The key is unknown or revoked. Keys start with sk_part_; request a new one. |
| Every verdict is ABSTAIN | The required checks did not answer: see reasons and openRisks, and the status page for which intents have live miners. You are charged upstream cost only. |
UPSTREAM_UNAVAILABLE | A required intent has no live miner right now. Retry later, or choose a policy that does not require it. |
TIMEOUT | Upstream answers were slow. The verdict may still seal: call wait(id) with the id from the error's details. |
| Telegraph: insufficient balance | Deposit more test USDC into escrow (section 09, step 4) and check effectiveBalance. |
| Telegraph: challenge refused | The price was above your client's maxUusdcPerCall, or the challenge failed its checks. Nothing was signed. |
| MCP tools don't appear | Restart the client after editing its config, and check its MCP log: the server prints skein-mcp ready to stderr. |
13 · NextWhere next
- Run one in the consoleWatch the threads form, then open the verdict page.
- Read the policiesEvery blocker, weight and threshold, versioned and immutable.
- API referenceEvery endpoint and error code.
- Source and issuesApache-2.0. Issues and pull requests welcome.
Packages: @skeinlabs/sdk · @skeinlabs/mcp. Found a mistake in this tutorial? Open an issue.