@blockrun/franklin 3.15.52 → 3.15.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -462,7 +462,13 @@ export function createVideoGenCapability(deps = {}) {
462
462
  properties: {
463
463
  prompt: { type: 'string', description: 'Text description of the video to generate' },
464
464
  output_path: { type: 'string', description: 'Where to save the MP4. Default: generated-<timestamp>.mp4 in working directory' },
465
- model: { type: 'string', description: 'Video model. Default: xai/grok-imagine-video' },
465
+ model: {
466
+ type: 'string',
467
+ description: 'Video model. Default: xai/grok-imagine-video. Known-valid models on the BlockRun gateway as of 2026-05: ' +
468
+ 'xai/grok-imagine-video, bytedance/seedance-1.5-pro, bytedance/seedance-2.0, bytedance/seedance-2.0-fast. ' +
469
+ 'Pick from this list; the gateway rejects unknown names with HTTP 400 (no money charged on rejection). ' +
470
+ 'Speak "Seedance Pro" → bytedance/seedance-2.0; speak "Seedance fast" → bytedance/seedance-2.0-fast.',
471
+ },
466
472
  image_url: { type: 'string', description: 'Optional seed image (image-to-video). Accepts http(s) URL, data: URI, or local file path — local paths get inlined as base64 data URIs automatically.' },
467
473
  duration_seconds: { type: 'number', description: 'Duration billed for. Default depends on model (8s for grok-imagine-video).' },
468
474
  contentId: { type: 'string', description: 'Optional Content id to attach and budget against.' },
@@ -7,7 +7,26 @@
7
7
  */
8
8
  import type { ProviderError } from '../standard-models.js';
9
9
  export declare const TICKER_TO_ID: Record<string, string>;
10
+ /** For tests + cache invalidation. */
11
+ export declare function clearIdResolutionCache(): void;
12
+ /**
13
+ * Resolve a ticker to its CoinGecko id. Synchronous — checks the static
14
+ * map and the dynamic cache. Falls through to lowercase as a final guess.
15
+ *
16
+ * Use this from `transformData` (which is sync). Use `resolveProviderIdAsync`
17
+ * from `fetchData` to populate the cache before the sync read happens.
18
+ */
10
19
  export declare function resolveProviderId(ticker: string): string;
20
+ /**
21
+ * Like `resolveProviderId`, but on a static-map miss, hits CoinGecko's
22
+ * `/search?query=` to find the canonical id. Caches the result for 7 days
23
+ * so `resolveProviderId` (sync) can read it back during `transformData`.
24
+ *
25
+ * Why not always async: `transformData` is part of the Fetcher contract
26
+ * and is intentionally sync. `fetchData` is async, runs first, and is the
27
+ * right place to do network resolution. The two share state via the cache.
28
+ */
29
+ export declare function resolveProviderIdAsync(ticker: string): Promise<string>;
11
30
  export declare function cached<T>(key: string, ttlMs: number, fn: () => Promise<T>): Promise<T>;
12
31
  /** For tests: wipe every cached entry. */
13
32
  export declare function clearCache(): void;
@@ -11,7 +11,15 @@ const BASE = 'https://api.coingecko.com/api/v3';
11
11
  const UA = `franklin/${VERSION} (trading)`;
12
12
  const TIMEOUT_MS = 10_000;
13
13
  // Ticker → CoinGecko slug. Not exhaustive; unknown tickers fall through to
14
- // lowercase and let CoinGecko either accept the slug or 404.
14
+ // the dynamic /search resolver below, which caches results.
15
+ //
16
+ // Verified 2026-05-04 in a live session: user asked Franklin for TON price,
17
+ // TradingMarket returned "No CoinGecko data for TON" because TON wasn't in
18
+ // this map and the lowercase fallback ("ton") doesn't match CoinGecko's
19
+ // actual id ("the-open-network"). Same hole exists for any token whose
20
+ // symbol differs from its id slug. Expanded the static map to cover the
21
+ // top ~30 currently-missing tokens, and added a /search-based resolver
22
+ // for everything else.
15
23
  export const TICKER_TO_ID = {
16
24
  BTC: 'bitcoin', ETH: 'ethereum', SOL: 'solana', BNB: 'binancecoin', XRP: 'ripple',
17
25
  ADA: 'cardano', DOGE: 'dogecoin', AVAX: 'avalanche-2', DOT: 'polkadot', MATIC: 'matic-network',
@@ -20,12 +28,87 @@ export const TICKER_TO_ID = {
20
28
  FIL: 'filecoin', AAVE: 'aave', MKR: 'maker', SNX: 'synthetix-network-token',
21
29
  COMP: 'compound-governance-token', INJ: 'injective-protocol', TIA: 'celestia',
22
30
  PEPE: 'pepe', WIF: 'dogwifcoin', RENDER: 'render-token',
31
+ // ── Added 2026-05-04 after live "No CoinGecko data for TON" report ──
32
+ TON: 'the-open-network', HYPE: 'hyperliquid', TRX: 'tron', TAO: 'bittensor',
33
+ WLD: 'worldcoin-wld', ENA: 'ethena', BERA: 'berachain-bera', JUP: 'jupiter-exchange-solana',
34
+ FET: 'fetch-ai', ONDO: 'ondo-finance', RNDR: 'render-token',
35
+ USDT: 'tether', USDC: 'usd-coin', DAI: 'dai', BCH: 'bitcoin-cash', ETC: 'ethereum-classic',
36
+ XLM: 'stellar', XMR: 'monero', IMX: 'immutable-x', GRT: 'the-graph', SAND: 'the-sandbox',
37
+ MANA: 'decentraland', AXS: 'axie-infinity', KAS: 'kaspa', ICP: 'internet-computer',
38
+ HBAR: 'hedera-hashgraph', VET: 'vechain', ALGO: 'algorand', FTM: 'fantom',
39
+ EGLD: 'elrond-erd-2', CRV: 'curve-dao-token', LDO: 'lido-dao', SHIB: 'shiba-inu',
40
+ BONK: 'bonk', POPCAT: 'popcat', FLOKI: 'floki', PNUT: 'peanut-the-squirrel',
23
41
  };
42
+ const ID_RESOLUTION_CACHE = new Map();
43
+ const ID_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
44
+ function normalizeTicker(ticker) {
45
+ return ticker.toUpperCase().replace(/-USD$/, '').replace(/USDT?$/, '');
46
+ }
47
+ /** For tests + cache invalidation. */
48
+ export function clearIdResolutionCache() {
49
+ ID_RESOLUTION_CACHE.clear();
50
+ }
51
+ /**
52
+ * Resolve a ticker to its CoinGecko id. Synchronous — checks the static
53
+ * map and the dynamic cache. Falls through to lowercase as a final guess.
54
+ *
55
+ * Use this from `transformData` (which is sync). Use `resolveProviderIdAsync`
56
+ * from `fetchData` to populate the cache before the sync read happens.
57
+ */
24
58
  export function resolveProviderId(ticker) {
25
- // Accept both "BTC" and "BTC-USD" — Pyth-style callers may pass the pair
26
- // form even when the registry routes them to CoinGecko.
27
- const normalized = ticker.toUpperCase().replace(/-USD$/, '').replace(/USDT?$/, '');
28
- return TICKER_TO_ID[normalized] ?? TICKER_TO_ID[ticker.toUpperCase()] ?? normalized.toLowerCase();
59
+ const normalized = normalizeTicker(ticker);
60
+ if (TICKER_TO_ID[normalized])
61
+ return TICKER_TO_ID[normalized];
62
+ if (TICKER_TO_ID[ticker.toUpperCase()])
63
+ return TICKER_TO_ID[ticker.toUpperCase()];
64
+ const cached = ID_RESOLUTION_CACHE.get(normalized);
65
+ if (cached && cached.expiresAt > Date.now())
66
+ return cached.id;
67
+ return normalized.toLowerCase();
68
+ }
69
+ /**
70
+ * Like `resolveProviderId`, but on a static-map miss, hits CoinGecko's
71
+ * `/search?query=` to find the canonical id. Caches the result for 7 days
72
+ * so `resolveProviderId` (sync) can read it back during `transformData`.
73
+ *
74
+ * Why not always async: `transformData` is part of the Fetcher contract
75
+ * and is intentionally sync. `fetchData` is async, runs first, and is the
76
+ * right place to do network resolution. The two share state via the cache.
77
+ */
78
+ export async function resolveProviderIdAsync(ticker) {
79
+ const normalized = normalizeTicker(ticker);
80
+ // Static map and dynamic cache — fast path.
81
+ if (TICKER_TO_ID[normalized])
82
+ return TICKER_TO_ID[normalized];
83
+ if (TICKER_TO_ID[ticker.toUpperCase()])
84
+ return TICKER_TO_ID[ticker.toUpperCase()];
85
+ const cached = ID_RESOLUTION_CACHE.get(normalized);
86
+ if (cached && cached.expiresAt > Date.now())
87
+ return cached.id;
88
+ // Network: ask CoinGecko's search endpoint.
89
+ try {
90
+ const result = await coingeckoGet(`/search?query=${encodeURIComponent(normalized)}`);
91
+ if (result && typeof result === 'object' && !('kind' in result) && 'coins' in result) {
92
+ const coins = result.coins;
93
+ if (Array.isArray(coins) && coins.length > 0) {
94
+ // Prefer an exact symbol match; fall back to the highest-ranked
95
+ // coin (lowest market_cap_rank value, ignoring null/undefined).
96
+ const exact = coins.find(c => c.symbol?.toUpperCase() === normalized && typeof c.id === 'string');
97
+ const fallback = [...coins]
98
+ .filter((c) => typeof c.id === 'string')
99
+ .sort((a, b) => (a.market_cap_rank ?? Infinity) - (b.market_cap_rank ?? Infinity))[0];
100
+ const resolved = exact?.id ?? fallback?.id;
101
+ if (resolved) {
102
+ ID_RESOLUTION_CACHE.set(normalized, { id: resolved, expiresAt: Date.now() + ID_TTL_MS });
103
+ return resolved;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ catch {
109
+ // /search itself failed — fall through to the lowercase guess.
110
+ }
111
+ return normalized.toLowerCase();
29
112
  }
30
113
  const cache = new Map();
31
114
  export async function cached(key, ttlMs, fn) {
@@ -1,4 +1,4 @@
1
- import { cached, coingeckoGet, resolveProviderId, TTL } from './client.js';
1
+ import { cached, coingeckoGet, resolveProviderIdAsync, TTL } from './client.js';
2
2
  export const coingeckoOHLCVFetcher = {
3
3
  providerName: 'coingecko',
4
4
  transformQuery(input) {
@@ -9,7 +9,7 @@ export const coingeckoOHLCVFetcher = {
9
9
  return { ticker, days };
10
10
  },
11
11
  async fetchData(query) {
12
- const id = resolveProviderId(query.ticker);
12
+ const id = await resolveProviderIdAsync(query.ticker);
13
13
  return cached(`ohlcv:${id}:${query.days}`, TTL.ohlcv, async () => {
14
14
  return coingeckoGet(`/coins/${id}/market_chart?vs_currency=usd&days=${query.days}&interval=daily`);
15
15
  });
@@ -6,7 +6,7 @@
6
6
  * Coerces: the nested `{ [id]: { usd: ..., usd_24h_change: ... } }`
7
7
  * response into the standard PriceData shape.
8
8
  */
9
- import { cached, coingeckoGet, resolveProviderId, TTL } from './client.js';
9
+ import { cached, coingeckoGet, resolveProviderId, resolveProviderIdAsync, TTL } from './client.js';
10
10
  export const coingeckoPriceFetcher = {
11
11
  providerName: 'coingecko',
12
12
  transformQuery(input) {
@@ -17,7 +17,10 @@ export const coingeckoPriceFetcher = {
17
17
  return { ticker };
18
18
  },
19
19
  async fetchData(query) {
20
- const id = resolveProviderId(query.ticker);
20
+ // resolveProviderIdAsync warms the dynamic id cache via /search when the
21
+ // ticker isn't in the static map (e.g. TON → the-open-network).
22
+ // transformData below reads back from the same cache synchronously.
23
+ const id = await resolveProviderIdAsync(query.ticker);
21
24
  return cached(`price:${id}`, TTL.price, async () => {
22
25
  return coingeckoGet(`/simple/price?ids=${id}` +
23
26
  `&vs_currencies=usd&include_24hr_change=true` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.52",
3
+ "version": "3.15.54",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {