Skip to content

Commit 041972f

Browse files
trilltinoclaude
andcommitted
feat(txodds): one-screen e2e demo — agents bid & settle a live World Cup edge
One "Start a market" click drives the whole thesis: real World Cup odds -> LLM edge -> agents bid over CoralOS -> Solana escrow settles, all on one screen. - coral-agents/seller-worldcup: a SERVICES=txline specialist persona (reuses the seller image, auto-registers from /agents/*). - seller: txline `edge` now delivers the de-margined 1X2 market alongside the LLM value call; a bare fixture id means `edge`; two empty-string-default fixes (TXLINE_BASE_URL + INFERENCE_MODEL use `||` not `??`, since coral injects "" for unset options). - marketplace/start.ts: launches seller-worldcup when TXLINE_API_KEY is set, forwards the token, passes BUYER_ARG. - dashboard: WorldCupPanel renders the odds board + the edge call in RoundCard for txline-edge deliveries. - docs: DEMO.md + E2E_DEMO.md runbooks. Verified live on devnet end to end: WANT(txline) -> generalists decline -> seller-worldcup wins -> DEPOSIT -> real odds + Claude value call -> RELEASE. Croatia v Ghana: Home 37% / Draw 47% / Away 16%; settled with release tx 4jFAch89. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c234789 commit 041972f

10 files changed

Lines changed: 258 additions & 12 deletions

File tree

coral-agents/seller-agent/src/service.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export async function deliverService(request: string): Promise<string> {
4848
async function claudeInference(request: string): Promise<string> {
4949
const key = process.env.ANTHROPIC_API_KEY
5050
if (!key) return JSON.stringify({ error: 'ANTHROPIC_API_KEY not set' })
51-
const model = process.env.INFERENCE_MODEL ?? 'claude-opus-4-8'
51+
const model = process.env.INFERENCE_MODEL || 'claude-opus-4-8' // `||`: toml passes "" for unset options
5252

5353
const res = await fetch('https://api.anthropic.com/v1/messages', {
5454
method: 'POST',
@@ -140,7 +140,8 @@ async function newsHeadlines(request: string): Promise<string> {
140140
// "edge <fixtureId>" → odds + an LLM value call (the on-thesis, all-three-pillars product)
141141
// Verified live on devnet (2026-06): host txline-dev.txodds.com; odds path is /api/odds/snapshot/{id};
142142
// every call needs the guest JWT *and* the activated X-Api-Token.
143-
const TXLINE_BASE = process.env.TXLINE_BASE_URL ?? 'https://txline-dev.txodds.com'
143+
// `||` not `??`: coral passes an empty-string default for unset options, which `??` would not catch.
144+
const TXLINE_BASE = process.env.TXLINE_BASE_URL || 'https://txline-dev.txodds.com'
144145

145146
async function txlineGet(path: string): Promise<unknown> {
146147
const apiToken = process.env.TXLINE_API_KEY
@@ -156,20 +157,27 @@ async function txlineGet(path: string): Promise<unknown> {
156157
}
157158

158159
async function txlineService(request: string): Promise<string> {
159-
const [verb, ...rest] = request.trim().split(/\s+/)
160-
const fixtureId = rest[0]
161-
switch ((verb || 'fixtures').toLowerCase()) {
160+
const tokens = request.trim().split(/\s+/).filter(Boolean)
161+
// A bare fixture id (single numeric token) is treated as `edge <id>` — the on-thesis product, and it
162+
// survives the single-token WANT `arg` the marketplace broadcasts (e.g. BUYER_ARG=17588245).
163+
let verb = (tokens[0] ?? 'fixtures').toLowerCase()
164+
let fixtureId = tokens[1]
165+
if (/^\d+$/.test(verb)) { fixtureId = verb; verb = 'edge' }
166+
switch (verb) {
162167
case 'odds':
163168
return JSON.stringify({ service: 'txline-odds', fixtureId, odds: await txlineGet(`/api/odds/snapshot/${fixtureId}`) })
164169
case 'edge': {
165170
const odds = await txlineGet(`/api/odds/snapshot/${fixtureId}`)
171+
// Pull the 1X2 de-margined market so the buyer/UI gets the odds board, not just the call.
172+
const m = Array.isArray(odds) ? (odds as Array<Record<string, unknown>>).find((x) => String(x.SuperOddsType ?? '').includes('1X2')) : undefined
173+
const market = m ? { names: m.PriceNames, pct: m.Pct } : undefined
166174
const raw = await claudeInference(
167175
'You are a football trading analyst. From these de-margined World Cup odds, return JSON ' +
168176
`{call, confidence} — a one-line value call and a 0-1 confidence. Odds: ${JSON.stringify(odds).slice(0, 1500)}`,
169177
)
170178
let analysis: unknown
171179
try { analysis = JSON.parse(raw).completion ?? raw } catch { analysis = raw }
172-
return JSON.stringify({ service: 'txline-edge', fixtureId, analysis })
180+
return JSON.stringify({ service: 'txline-edge', fixtureId, market, analysis })
173181
}
174182
case 'fixtures':
175183
default: {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
edition = 4
2+
3+
[agent]
4+
name = "seller-worldcup"
5+
version = "0.1.0"
6+
description = "Marketplace seller persona — World Cup oracle specialist. Reuses the seller-agent image; sells verified TxODDS World Cup data (the `txline` service) and an LLM value call."
7+
summary = "A specialist that bids only on World Cup data jobs and delivers an LLM-analysed edge."
8+
readme = "Bids in the shared market thread when the WANT is a `txline` service, wins because the generalists decline it, fetches de-margined World Cup odds from TxLINE, runs them through the LLM, and settles via escrow. Same image as seller-agent; SERVICES=txline + TXLINE_API_KEY shape it."
9+
10+
[agent.license]
11+
type = "spdx"
12+
expression = "MIT"
13+
14+
[options]
15+
SELLER_WALLET = { type = "string", description = "Devnet wallet pubkey that receives payments" }
16+
AGENT_NAME = { type = "string", default = "seller-worldcup", description = "Market identity (matches the registry name)" }
17+
SERVICES = { type = "string", default = "txline", description = "Inventory — this specialist bids only on txline (World Cup data)" }
18+
FLOOR_SOL = { type = "f64", default = 0.0005, description = "Cost floor — the LLM may never bid below this (the edge runs a real Claude call)" }
19+
PERSONA = { type = "string", default = "a World Cup trading specialist who sells verified TxODDS odds and a sharp one-line value call", description = "Bidding persona (LLM system prompt)" }
20+
SERVICE = { type = "string", default = "txline", description = "What deliverService returns: txline (fixtures | odds <id> | edge <id>)" }
21+
SOLANA_RPC_URL = { type = "string", default = "", description = "Solana RPC URL (devnet)" }
22+
ESCROW_DEADLINE_SECS = { type = "f64", default = 600, description = "Refund deadline for escrow deposits (seconds)" }
23+
TXLINE_API_KEY = { type = "string", default = "", description = "Activated TxLINE API token (from the one-time devnet subscribe — see examples/txodds)" }
24+
TXLINE_BASE_URL = { type = "string", default = "", description = "Override the TxLINE host (defaults to txline-dev.txodds.com)" }
25+
ANTHROPIC_API_KEY = { type = "string", default = "", description = "Anthropic key — the edge verb runs a Claude completion" }
26+
OPENAI_API_KEY = { type = "string", default = "", description = "OpenAI key — set with LLM_PROVIDER=openai to flip the brain" }
27+
LLM_PROVIDER = { type = "string", default = "", description = "anthropic (default) | openai" }
28+
LLM_MODEL = { type = "string", default = "", description = "Override the LLM model id" }
29+
INFERENCE_MODEL = { type = "string", default = "", description = "Model for the edge's Claude call (default claude-opus-4-8)" }
30+
TRACE = { type = "string", default = "", description = "Set to 1 to log CoralOS calls + on-chain steps" }
31+
32+
# Reuses the already-built seller-agent image — no separate build needed.
33+
[runtimes.docker]
34+
image = "seller-agent:0.1.0"

examples/marketplace/start.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,28 @@ async function main() {
6666
const seller = (name: string) =>
6767
agent(name, { SELLER_WALLET: str(wallet), SOLANA_RPC_URL: str(rpc), AGENT_NAME: str(name), ...llmOpts })
6868

69+
// World Cup specialist — only joins when a TxLINE token is present (see examples/txodds/DEMO.md).
70+
// It carries SERVICES=txline + the token; the generalist personas decline txline WANTs automatically.
71+
const txlineKey = env.TXLINE_API_KEY
72+
const worldcup = txlineKey
73+
? [agent('seller-worldcup', {
74+
SELLER_WALLET: str(wallet), SOLANA_RPC_URL: str(rpc), AGENT_NAME: str('seller-worldcup'),
75+
SERVICES: str('txline'), FLOOR_SOL: f64(Number(env.WORLDCUP_FLOOR_SOL ?? '0.0005')),
76+
TXLINE_API_KEY: str(txlineKey),
77+
...(env.TXLINE_BASE_URL ? { TXLINE_BASE_URL: str(env.TXLINE_BASE_URL) } : {}),
78+
...llmOpts,
79+
})]
80+
: []
81+
82+
const sellers = ['seller-cheap', 'seller-premium', 'seller-lazy', ...(txlineKey ? ['seller-worldcup'] : [])]
6983
const buyerOpts: Record<string, unknown> = {
7084
BUYER_KEYPAIR_B58: str(keypair),
7185
AGENT_NAME: str('buyer-agent'),
7286
SOLANA_RPC_URL: str(rpc),
7387
BUYER_MAX_SOL: f64(Number(env.BUYER_MAX_SOL ?? '0.001')),
7488
BUYER_SERVICE: str(env.BUYER_SERVICE ?? 'coingecko'),
75-
MARKET_SELLERS: str('seller-cheap,seller-premium,seller-lazy'),
89+
BUYER_ARG: str(env.BUYER_ARG ?? 'SOL-USDC'),
90+
MARKET_SELLERS: str(sellers.join(',')),
7691
...llmOpts,
7792
}
7893

@@ -85,6 +100,7 @@ async function main() {
85100
seller('seller-cheap'),
86101
seller('seller-premium'),
87102
seller('seller-lazy'),
103+
...worldcup,
88104
],
89105
},
90106
namespaceProvider: { type: 'create_if_not_exists', namespaceRequest: { name: NS } },
@@ -94,7 +110,7 @@ async function main() {
94110
if (!sres.ok) throw new Error(`session create failed: ${sres.status} ${await sres.text()}`)
95111
const { sessionId } = await sres.json() as { sessionId: string }
96112

97-
console.log(`\n✅ Market session ${sessionId} — buyer + seller-cheap/-premium/-lazy.`)
113+
console.log(`\n✅ Market session ${sessionId} — buyer + ${sellers.join(', ')}.`)
98114
console.log(` receive wallet: ${wallet}`)
99115
console.log(' The buyer broadcasts a WANT; sellers bid; the winner settles via escrow.\n')
100116
console.log(' Watch the market:')

examples/marketplace/web/src/components/RoundCard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Round } from '../types'
22
import { StatusPill } from './StatusPill'
33
import { BidRow, DeclinedRow } from './BidRow'
44
import { SettlementBadge } from './SettlementBadge'
5+
import { WorldCupPanel } from './WorldCupPanel'
56

67
/** One auction round: the need, the competing bids, the award + reasoning, and on-chain settlement. */
78
export function RoundCard({ round }: { round: Round }) {
@@ -35,7 +36,9 @@ export function RoundCard({ round }: { round: Round }) {
3536
)}
3637

3738
{round.delivered && (
38-
<pre className="delivered" data-testid="delivered">{round.delivered.raw}</pre>
39+
(round.delivered.data as { service?: string } | undefined)?.service === 'txline-edge'
40+
? <WorldCupPanel edge={round.delivered.data as Parameters<typeof WorldCupPanel>[0]['edge']} />
41+
: <pre className="delivered" data-testid="delivered">{round.delivered.raw}</pre>
3942
)}
4043

4144
<footer className="settle-row">
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/** Renders a txline-edge delivery: the de-margined 1X2 odds board + the LLM value call. */
2+
interface Edge {
3+
service: string
4+
fixtureId?: string | number
5+
market?: { names?: string[]; pct?: string[] }
6+
analysis?: unknown
7+
}
8+
9+
const LABEL: Record<string, string> = { part1: 'Home', draw: 'Draw', part2: 'Away' }
10+
11+
/** The seller's `analysis` may be an object, a JSON string {call, confidence}, or plain prose. */
12+
function parseAnalysis(a: unknown): { call?: string; confidence?: number } {
13+
if (a && typeof a === 'object') return a as { call?: string; confidence?: number }
14+
if (typeof a === 'string') {
15+
try {
16+
const o = JSON.parse(a)
17+
return o && typeof o === 'object' ? o : { call: a }
18+
} catch {
19+
return { call: a }
20+
}
21+
}
22+
return {}
23+
}
24+
25+
export function WorldCupPanel({ edge }: { edge: Edge }) {
26+
const names = edge.market?.names ?? []
27+
const pct = edge.market?.pct ?? []
28+
const { call, confidence } = parseAnalysis(edge.analysis)
29+
return (
30+
<div className="wc-panel" data-testid="wc-edge">
31+
<div className="wc-head">⚽ World Cup edge · fixture {edge.fixtureId}</div>
32+
{names.length > 0 && (
33+
<div className="wc-odds">
34+
{names.map((name, i) => {
35+
const p = Number(pct[i])
36+
return (
37+
<div className="wc-row" key={name}>
38+
<span className="wc-sel">{LABEL[name] ?? name}</span>
39+
<span className="wc-pct">{Number.isFinite(p) ? `${p.toFixed(0)}%` : '—'}</span>
40+
<div className="wc-bar"><div className="wc-fill" style={{ width: `${Math.min(100, Number.isFinite(p) ? p : 0)}%` }} /></div>
41+
</div>
42+
)
43+
})}
44+
</div>
45+
)}
46+
{call && (
47+
<p className="wc-call">
48+
<strong>call:</strong> {call}
49+
{confidence != null && <span className="wc-conf"> · {Math.round(Number(confidence) * 100)}% conf</span>}
50+
</p>
51+
)}
52+
</div>
53+
)
54+
}

examples/marketplace/web/src/styles.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,16 @@ body { margin: 0; background: var(--bg); color: var(--txt); }
5555
.settle { color: var(--link); text-decoration: none; font-size: 12px; border: 1px solid var(--line); padding: 4px 8px; border-radius: 6px; }
5656
.settle:hover { border-color: var(--link); }
5757
.settle-refund { color: var(--refund); }
58+
59+
/* World Cup edge panel (txline-edge deliveries) */
60+
.wc-panel { border: 1px solid var(--premium); border-radius: 10px; padding: 10px 12px; margin: 6px 0 10px; background: var(--bg); }
61+
.wc-head { font-size: 12px; color: var(--premium); margin-bottom: 10px; letter-spacing: .02em; }
62+
.wc-odds { display: flex; flex-direction: column; gap: 8px; }
63+
.wc-row { display: grid; grid-template-columns: 64px 40px 1fr; align-items: center; gap: 8px; }
64+
.wc-sel { font-size: 13px; font-weight: 600; }
65+
.wc-pct { font-size: 12px; color: var(--link); font-variant-numeric: tabular-nums; text-align: right; }
66+
.wc-bar { height: 6px; background: var(--panel); border-radius: 999px; overflow: hidden; }
67+
.wc-fill { height: 100%; background: linear-gradient(90deg, var(--link), var(--won)); border-radius: 999px; }
68+
.wc-call { margin: 12px 0 2px; font-size: 13px; color: var(--txt); border-top: 1px dashed var(--line); padding-top: 10px; }
69+
.wc-call strong { color: var(--premium); }
70+
.wc-conf { color: var(--dim); }

examples/txodds/DEMO.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Live demo: an agent sells a World Cup edge for devnet SOL
2+
3+
This is the "money shot" round — Demo A (the marketplace) settling Demo B (real TxODDS data) in
4+
**one live round**: the buyer wants a World Cup edge, a specialist seller fetches verified odds, an LLM
5+
turns them into a value call, and the buyer pays for it through the escrow. **Config only — no agent
6+
code changes** (the `txline` service is already in the seller image).
7+
8+
## The pieces
9+
10+
| Piece | What changes | Why |
11+
|---|---|---|
12+
| `coral-agents/seller-worldcup/coral-agent.toml` | **new persona** over the seller image, `SERVICES=txline` | a seller that bids on `txline` (auto-registered from `/agents/*`) |
13+
| `examples/marketplace/start.ts` | add `seller-worldcup` to the session, forward `TXLINE_API_KEY`, pass `BUYER_ARG` | so the persona launches with the token and the buyer's WANT carries the fixture |
14+
| `.env` | `TXLINE_API_KEY`, `BUYER_SERVICE=txline`, `BUYER_ARG=edge 17588245` | the token (one-time mint) + the WANT the buyer broadcasts |
15+
16+
## How a round resolves
17+
18+
```
19+
buyer WANT service=txline arg="edge 17588245" budget=0.001
20+
seller-cheap …silent (txline not in [jupiter,coingecko]) ← non-specialists decline
21+
seller-worldcup BID price=0.0005 note="verified odds + edge"
22+
buyer AWARD → seller-worldcup
23+
seller-worldcup ESCROW_REQUIRED → buyer DEPOSIT → on-chain
24+
seller-worldcup deliverService("txline edge 17588245")
25+
→ fetch de-margined odds (txline-dev) → Claude value call
26+
→ DELIVERED {"call":"…","confidence":…}
27+
buyer RELEASED 0.0005 SOL → seller-worldcup (real devnet tx)
28+
```
29+
30+
The bidder's hard guard (`bidder.ts`: `!services.includes(want.service)`) makes the non-specialist
31+
personas sit out automatically, so the World Cup specialist wins — a clean "right agent for the job"
32+
story.
33+
34+
## Run it
35+
36+
```sh
37+
# 1. mint a fresh TXLINE_API_KEY into .env (one-time subscribe+activate on devnet)
38+
# — done by scripts in examples/txodds; the token is the only thing the seller needs
39+
# 2. set the WANT in .env:
40+
# BUYER_SERVICE=txline
41+
# BUYER_ARG=edge 17588245 # Croatia v Ghana
42+
# 3. rebuild not needed (txline already in the image). Fresh session:
43+
docker compose up -d coral
44+
cd examples/marketplace && npm start
45+
docker logs -f <buyer-container> # WANT(txline) → AWARD seller-worldcup → DEPOSITED → RELEASED
46+
```
47+
48+
## Notes
49+
50+
- The activated API token is time-limited (tied to the 4-week free subscription) — mint it fresh
51+
shortly before demoing.
52+
- `seller-worldcup` reuses the **already-built `seller-agent:0.1.0` image**; only its persona/options
53+
differ, so there is no rebuild.
54+
- Fixture `17588245` is Croatia v Ghana; swap `BUYER_ARG` for any live fixture id from
55+
`GET /api/fixtures/snapshot`.

examples/txodds/E2E_DEMO.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Full e2e demo — one page, one button, the whole thesis
2+
3+
Goal: a judge clicks **one button** and watches, on **one screen**, the complete loop:
4+
5+
> **real World Cup odds → LLM edge → agents bid over CoralOS → Solana escrow settles → release**
6+
7+
This reuses the kit's existing marketplace **dashboard** (which already has the button, the live round
8+
feed, bids, and Explorer-linked settlement) and makes it show the *World Cup* data the round trades.
9+
10+
## Why this is small
11+
12+
The dashboard already renders a live round from the feed: `WANT → BID → AWARD → DEPOSITED → DELIVERED
13+
→ RELEASED`, with deposit/release linked to the devnet Explorer. And the round object already carries
14+
`want.arg` (the fixture id) and `delivered.data` (the seller's edge JSON). Two changes make it the
15+
World Cup demo:
16+
17+
| Change | File | Why |
18+
|---|---|---|
19+
| `edge` delivers the de-margined odds, not just the call | `coral-agents/seller-agent/src/service.ts` | so the round's `delivered.data` carries the 1X2 board |
20+
| RoundCard renders a World Cup panel for `txline-edge` | `examples/marketplace/web/src/components/RoundCard.tsx` | the odds bars + the LLM value call, in the same card as the settlement |
21+
22+
No new backend: the feed's **Start** button already runs `start.ts`, which is World-Cup-configured
23+
(`seller-worldcup` + `BUYER_SERVICE=txline` + `BUYER_ARG=<fixture>` when `TXLINE_API_KEY` is set).
24+
25+
## What the one screen shows
26+
27+
```
28+
[ The Agent Marketplace ] (•) connected
29+
────────────────────────────────────────────────────────────
30+
[ Start a market ]
31+
32+
Round 1 — WANT txline 17588245 (Croatia v Ghana)
33+
seller-cheap declined (not in inventory)
34+
seller-worldcup BID 0.0005 "verified odds + edge" ← specialist wins
35+
AWARD → seller-worldcup
36+
╔══ World Cup edge ═══════════════════════════════╗
37+
║ Croatia ███████████ 36% ║ ← real de-margined odds
38+
║ Draw ████████████████ 48% ║
39+
║ Ghana █████ 16% ║
40+
║ call: "value on the draw" · confidence 0.6 ║ ← the LLM edge
41+
╚══════════════════════════════════════════════════╝
42+
deposit 0.0005 SOL ↗explorer · release ↗explorer ← real devnet txns
43+
```
44+
45+
## Run it
46+
47+
```sh
48+
# 0. one-time: TXLINE_API_KEY in .env (mint via examples/txodds) + BUYER_SERVICE=txline + BUYER_ARG=<id>
49+
docker compose up -d coral # coordinator
50+
node scripts/dashboard.js # feed (:4000) + dashboard (:5173), opens the browser
51+
# click "Start a market" → watch the World Cup round bid + settle live
52+
```
53+
54+
## Robust fallback (no live devnet)
55+
56+
The feed supports `FEED_FIXTURE=<recorded-extended-state.json>` — serve a recorded World Cup round so
57+
the dashboard renders the **real fold/parse path** deterministically, with no on-chain calls. Use this
58+
when demoing on flaky wifi.

examples/txodds/agent/service.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@ import { TxLineClient } from './txline.js'
2222
import { complete } from '@pay/agent-runtime'
2323

2424
export async function deliverTxOdds(request: string): Promise<string> {
25-
const [verb = 'fixtures', ...rest] = request.trim().split(/\s+/)
25+
const tokens = request.trim().split(/\s+/).filter(Boolean)
26+
// A bare fixture id (single numeric token) is treated as `edge <id>` — the on-thesis product, and it
27+
// survives the single-token WANT `arg` the marketplace broadcasts (e.g. BUYER_ARG=17588245).
28+
let verb = (tokens[0] ?? 'fixtures').toLowerCase()
29+
let rest = tokens.slice(1)
30+
if (/^\d+$/.test(verb)) { rest = [verb]; verb = 'edge' }
2631
const client = new TxLineClient()
2732

2833
try {
29-
switch (verb.toLowerCase()) {
34+
switch (verb) {
3035
case 'fixtures': {
3136
const fixtures = await client.fixtures()
3237
return JSON.stringify({

examples/txodds/agent/txline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class TxLineClient {
4141
private jwt: string | undefined
4242

4343
constructor(opts: TxLineOptions = {}) {
44-
this.baseUrl = opts.baseUrl ?? process.env.TXLINE_BASE_URL ?? DEVNET_BASE
44+
this.baseUrl = opts.baseUrl || process.env.TXLINE_BASE_URL || DEVNET_BASE
4545
this.apiToken = opts.apiToken ?? process.env.TXLINE_API_KEY
4646
}
4747

0 commit comments

Comments
 (0)