@blockrun/franklin 3.11.0 → 3.12.1

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.
@@ -205,6 +205,15 @@ You run on the BlockRun AI Gateway. When the user asks you to "test the BlockRun
205
205
  - \`GET /v1/models\` — full model catalog (id, owner, context window, pricing).
206
206
  - \`GET /v1/health/overview\` · \`/v1/health/regions\` · \`/v1/health/chain\` · \`/v1/health/models\` — gateway status.
207
207
 
208
+ **Trading & DeFi (mixed methods, x402-paid; new in v3.12.0)**
209
+ - \`GET /v1/defillama/protocols\` · \`/v1/defillama/protocol/{slug}\` · \`/v1/defillama/chains\` · \`/v1/defillama/yields\` — TVL / yield-pool data, Apache-2.0 source. \$0.005/call.
210
+ - \`GET /v1/defillama/prices/{coins}\` — token price lookup (coingecko:bitcoin, ethereum:0x..., solana:mint, comma-separated). \$0.001/call.
211
+ - \`POST /v1/solana/rpc\` — JSON-RPC passthrough to public mainnet-beta (getAccountInfo, getTokenSupply, sendTransaction, etc.). \$0.0005 per call (per element of a batch). Use this instead of running your own RPC infra.
212
+
213
+ **Solana DEX swap (Jupiter Ultra)**
214
+ - Use the **\`JupiterQuote\` and \`JupiterSwap\` built-in tools** — they call Jupiter's Ultra API directly from this process. The user is the first-party caller of Jupiter; we are not a gateway proxy here. A 20 bps platform fee is collected on-chain as part of the swap (Jupiter Referral Program — official integrator mechanism, not a hidden cost).
215
+ - Do NOT try to call \`/v1/jupiter/...\` on the BlockRun gateway — there is no such endpoint (Jupiter ToU forbids the gateway-proxy model).
216
+
208
217
  **Sandbox (POST, x402-paid)**
209
218
  - \`/v1/modal/{...path}\` — Modal GPU sandbox passthrough (create/exec/etc.).
210
219
  - \`/v1/pm/{...path}\` — prediction-market data passthrough.
@@ -43,6 +43,9 @@ export function parseSkill(content) {
43
43
  if (typeof fields['cost-receipt'] === 'boolean') {
44
44
  skill.costReceipt = fields['cost-receipt'];
45
45
  }
46
+ if (Array.isArray(fields.triggers)) {
47
+ skill.triggers = fields.triggers.filter((t) => typeof t === 'string');
48
+ }
46
49
  return { skill, warnings };
47
50
  }
48
51
  function extractFrontmatter(content) {
@@ -57,6 +60,7 @@ function extractFrontmatter(content) {
57
60
  const body = rest.slice(closeIdx + 1 + FRONTMATTER_FENCE.length + 1);
58
61
  return { frontmatter, body };
59
62
  }
63
+ const LIST_ITEM_RE = /^\s+-\s+(.+)$/;
60
64
  function parseFrontmatter(text) {
61
65
  const fields = {};
62
66
  const warnings = [];
@@ -65,6 +69,9 @@ function parseFrontmatter(text) {
65
69
  const line = lines[i];
66
70
  if (line.trim() === '' || line.trim().startsWith('#'))
67
71
  continue;
72
+ // YAML list continuation: skip — handled by the key-line that opened it.
73
+ if (LIST_ITEM_RE.test(line))
74
+ continue;
68
75
  const colon = line.indexOf(':');
69
76
  if (colon < 0) {
70
77
  return { error: `frontmatter line ${i + 1} is not key: value — got "${line}"` };
@@ -74,6 +81,30 @@ function parseFrontmatter(text) {
74
81
  if (key.length === 0) {
75
82
  return { error: `frontmatter line ${i + 1} has empty key` };
76
83
  }
84
+ // Empty value followed by indented `- item` lines = YAML list.
85
+ if (rawValue === '') {
86
+ const items = [];
87
+ let j = i + 1;
88
+ while (j < lines.length) {
89
+ const next = lines[j];
90
+ if (next.trim() === '') {
91
+ j++;
92
+ continue;
93
+ }
94
+ const m = LIST_ITEM_RE.exec(next);
95
+ if (!m)
96
+ break;
97
+ items.push(parseScalarString(m[1].trim()));
98
+ j++;
99
+ }
100
+ if (items.length > 0) {
101
+ fields[key] = items;
102
+ continue;
103
+ }
104
+ // No list followed; treat as empty string.
105
+ fields[key] = '';
106
+ continue;
107
+ }
77
108
  fields[key] = parseScalar(rawValue);
78
109
  }
79
110
  return { fields, warnings };
@@ -140,7 +171,10 @@ function parseScalar(raw) {
140
171
  return Number.parseInt(raw, 10);
141
172
  if (/^-?\d+\.\d+$/.test(raw))
142
173
  return Number.parseFloat(raw);
143
- // Strip surrounding quotes if present.
174
+ return parseScalarString(raw);
175
+ }
176
+ /** Strip surrounding quotes from a string-shaped value, leaving everything else as-is. */
177
+ function parseScalarString(raw) {
144
178
  if ((raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) ||
145
179
  (raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2)) {
146
180
  return raw.slice(1, -1);
@@ -21,6 +21,8 @@ export interface ParsedSkill {
21
21
  budgetCapUsd?: number;
22
22
  /** Franklin extension: append a paid-call receipt under the agent reply. */
23
23
  costReceipt?: boolean;
24
+ /** Trigger phrases that should auto-invoke this skill when matched. */
25
+ triggers?: string[];
24
26
  }
25
27
  export type ParseResult = {
26
28
  skill: ParsedSkill;
@@ -1,6 +1,17 @@
1
1
  ---
2
2
  name: budget-grill
3
3
  description: Wallet-aware grilling — interview me about a plan one question at a time, with each branch of the decision tree framed as a USDC cost impact
4
+ triggers:
5
+ - "grill my plan"
6
+ - "interview my plan"
7
+ - "budget review"
8
+ - "cost analysis"
9
+ - "wallet drain"
10
+ - "spending review"
11
+ - "cost impact"
12
+ - "plan review"
13
+ - "challenge my idea"
14
+ - "stress test plan"
4
15
  argument-hint: <plan or topic to grill on>
5
16
  cost-receipt: true
6
17
  ---
@@ -25,6 +25,7 @@ import { postToXCapability } from './posttox.js';
25
25
  import { moaCapability } from './moa.js';
26
26
  import { webhookPostCapability } from './webhook.js';
27
27
  import { walletCapability } from './wallet.js';
28
+ import { jupiterQuoteCapability, jupiterSwapCapability } from './jupiter.js';
28
29
  import { createTradingCapabilities } from './trading-execute.js';
29
30
  import { Portfolio } from '../trading/portfolio.js';
30
31
  import { RiskEngine } from '../trading/risk.js';
@@ -144,6 +145,8 @@ export const allCapabilities = [
144
145
  moaCapability,
145
146
  webhookPostCapability,
146
147
  walletCapability,
148
+ jupiterQuoteCapability,
149
+ jupiterSwapCapability,
147
150
  ];
148
151
  export { readCapability, writeCapability, editCapability, bashCapability, globCapability, grepCapability, webFetchCapability, webSearchCapability, taskCapability, detachCapability, };
149
152
  export { createSubAgentCapability } from './subagent.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Jupiter Ultra Swap — Solana DEX aggregator (built-in Franklin tool).
3
+ *
4
+ * We do NOT proxy through the BlockRun gateway — Jupiter's ToU forbids that.
5
+ * Instead the agent calls Jupiter's Ultra API directly from this process,
6
+ * embedding BlockRun's Referral Program identity in every order. The 20 bps
7
+ * platform fee flows on-chain to BlockRun's referral wallet at swap settlement
8
+ * (Jupiter's officially-supported integrator monetization mechanism — same one
9
+ * Phantom and other wallets use).
10
+ *
11
+ * Two tools exposed:
12
+ * - JupiterQuote — read-only price check (no AskUser, no signing)
13
+ * - JupiterSwap — full flow: order → AskUser confirm → sign → execute
14
+ *
15
+ * Reference implementation:
16
+ * https://github.com/Jupiter-DevRel/typescript-examples/tree/main/ultra/order-execute-with-referral-accounts
17
+ */
18
+ import type { CapabilityHandler } from '../agent/types.js';
19
+ export declare const jupiterQuoteCapability: CapabilityHandler;
20
+ export declare const jupiterSwapCapability: CapabilityHandler;
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Jupiter Ultra Swap — Solana DEX aggregator (built-in Franklin tool).
3
+ *
4
+ * We do NOT proxy through the BlockRun gateway — Jupiter's ToU forbids that.
5
+ * Instead the agent calls Jupiter's Ultra API directly from this process,
6
+ * embedding BlockRun's Referral Program identity in every order. The 20 bps
7
+ * platform fee flows on-chain to BlockRun's referral wallet at swap settlement
8
+ * (Jupiter's officially-supported integrator monetization mechanism — same one
9
+ * Phantom and other wallets use).
10
+ *
11
+ * Two tools exposed:
12
+ * - JupiterQuote — read-only price check (no AskUser, no signing)
13
+ * - JupiterSwap — full flow: order → AskUser confirm → sign → execute
14
+ *
15
+ * Reference implementation:
16
+ * https://github.com/Jupiter-DevRel/typescript-examples/tree/main/ultra/order-execute-with-referral-accounts
17
+ */
18
+ import { Keypair, VersionedTransaction } from '@solana/web3.js';
19
+ import { getOrCreateSolanaWallet, solanaKeyToBytes, } from '@blockrun/llm';
20
+ // ─── BlockRun Referral identity ───────────────────────────────────────────
21
+ // Set up via referral.jup.ag. Owns ATAs for USDC, wSOL, JUP, USDT, etc. Every
22
+ // swap routed through these tools deposits 20 bps of output to this wallet's
23
+ // matching ATA.
24
+ const BLOCKRUN_REFERRAL_ACCOUNT = 'DUGyfGMTAvyHtrvCa2qPE2KJd3qtGBe4ra7u6URne4xQ';
25
+ const BLOCKRUN_REFERRAL_FEE_BPS = 20; // 0.2% — Jupiter docs default; well below Phantom's 85 bps.
26
+ // ─── Ultra API endpoints ──────────────────────────────────────────────────
27
+ const ULTRA_BASE = 'https://lite-api.jup.ag/ultra/v1';
28
+ const ORDER_TIMEOUT_MS = 15_000;
29
+ const EXECUTE_TIMEOUT_MS = 30_000;
30
+ // ─── Symbol → mint shortcuts ──────────────────────────────────────────────
31
+ // Agents prefer "USDC" / "SOL" over 44-char base58 mint addresses. Anything
32
+ // not in this map is passed through verbatim — power users can drop in any
33
+ // mint they want.
34
+ const SYMBOL_TO_MINT = {
35
+ SOL: 'So11111111111111111111111111111111111111112', // wSOL
36
+ WSOL: 'So11111111111111111111111111111111111111112',
37
+ USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
38
+ USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
39
+ JUP: 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN',
40
+ BONK: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263',
41
+ WIF: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm',
42
+ TRUMP: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN',
43
+ PUMP: 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn',
44
+ };
45
+ const TOKEN_DECIMALS = {
46
+ // wSOL — 9
47
+ So11111111111111111111111111111111111111112: 9,
48
+ // USDC — 6
49
+ EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: 6,
50
+ // USDT — 6
51
+ Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: 6,
52
+ // JUP — 6
53
+ JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN: 6,
54
+ // BONK — 5
55
+ DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263: 5,
56
+ // WIF — 6
57
+ EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm: 6,
58
+ };
59
+ function resolveMint(input) {
60
+ const upper = input.trim().toUpperCase();
61
+ if (SYMBOL_TO_MINT[upper])
62
+ return SYMBOL_TO_MINT[upper];
63
+ return input.trim();
64
+ }
65
+ function decimalsFor(mint, fallback = 9) {
66
+ return TOKEN_DECIMALS[mint] ?? fallback;
67
+ }
68
+ function symbolFor(mint) {
69
+ for (const [sym, m] of Object.entries(SYMBOL_TO_MINT)) {
70
+ if (m === mint)
71
+ return sym;
72
+ }
73
+ return mint.slice(0, 4) + '…';
74
+ }
75
+ function toAtomicUnits(amount, decimals) {
76
+ // BigInt math — JavaScript number can lose precision for large lamport counts.
77
+ const scale = BigInt(10) ** BigInt(decimals);
78
+ const whole = BigInt(Math.floor(amount));
79
+ const fractional = BigInt(Math.round((amount - Math.floor(amount)) * Number(scale)));
80
+ return (whole * scale + fractional).toString();
81
+ }
82
+ function fromAtomicUnits(atomic, decimals) {
83
+ const value = typeof atomic === 'string' ? Number(atomic) : atomic;
84
+ return value / Math.pow(10, decimals);
85
+ }
86
+ async function ultraOrder(params) {
87
+ const url = new URL(`${ULTRA_BASE}/order`);
88
+ url.searchParams.set('inputMint', params.inputMint);
89
+ url.searchParams.set('outputMint', params.outputMint);
90
+ url.searchParams.set('amount', params.amount);
91
+ if (params.taker)
92
+ url.searchParams.set('taker', params.taker);
93
+ url.searchParams.set('referralAccount', BLOCKRUN_REFERRAL_ACCOUNT);
94
+ url.searchParams.set('referralFee', String(BLOCKRUN_REFERRAL_FEE_BPS));
95
+ const controller = new AbortController();
96
+ const timer = setTimeout(() => controller.abort(), ORDER_TIMEOUT_MS);
97
+ try {
98
+ const res = await fetch(url.toString(), {
99
+ method: 'GET',
100
+ headers: { Accept: 'application/json' },
101
+ signal: controller.signal,
102
+ });
103
+ if (!res.ok) {
104
+ const text = await res.text();
105
+ throw new Error(`Jupiter Ultra /order returned ${res.status}: ${text}`);
106
+ }
107
+ return (await res.json());
108
+ }
109
+ finally {
110
+ clearTimeout(timer);
111
+ }
112
+ }
113
+ async function ultraExecute(args) {
114
+ const controller = new AbortController();
115
+ const timer = setTimeout(() => controller.abort(), EXECUTE_TIMEOUT_MS);
116
+ try {
117
+ const res = await fetch(`${ULTRA_BASE}/execute`, {
118
+ method: 'POST',
119
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
120
+ body: JSON.stringify(args),
121
+ signal: controller.signal,
122
+ });
123
+ if (!res.ok) {
124
+ const text = await res.text();
125
+ throw new Error(`Jupiter Ultra /execute returned ${res.status}: ${text}`);
126
+ }
127
+ return (await res.json());
128
+ }
129
+ finally {
130
+ clearTimeout(timer);
131
+ }
132
+ }
133
+ async function loadSolanaKeypair() {
134
+ const wallet = await getOrCreateSolanaWallet();
135
+ const bytes = await solanaKeyToBytes(wallet.privateKey);
136
+ return Keypair.fromSecretKey(bytes);
137
+ }
138
+ function formatQuote(order) {
139
+ const inMint = order.inputMint;
140
+ const outMint = order.outputMint;
141
+ const inDec = decimalsFor(inMint);
142
+ const outDec = decimalsFor(outMint);
143
+ const inAmount = fromAtomicUnits(order.inAmount, inDec);
144
+ const outAmount = fromAtomicUnits(order.outAmount, outDec);
145
+ const inSym = symbolFor(inMint);
146
+ const outSym = symbolFor(outMint);
147
+ const impact = order.priceImpactPct
148
+ ? `${(Number(order.priceImpactPct) * 100).toFixed(3)}%`
149
+ : 'n/a';
150
+ const route = order.routePlan
151
+ ?.map((step) => step.swapInfo?.label)
152
+ .filter(Boolean)
153
+ .join(' → ') || 'Jupiter Ultra';
154
+ const rate = inAmount > 0 ? outAmount / inAmount : 0;
155
+ return [
156
+ `${inAmount.toFixed(Math.min(6, inDec))} ${inSym} → ${outAmount.toFixed(Math.min(6, outDec))} ${outSym}`,
157
+ `Rate: 1 ${inSym} ≈ ${rate.toPrecision(6)} ${outSym}`,
158
+ `Price impact: ${impact}`,
159
+ `Route: ${route}`,
160
+ `Platform fee: ${BLOCKRUN_REFERRAL_FEE_BPS} bps (BlockRun referral)`,
161
+ ].join('\n');
162
+ }
163
+ async function executeJupiterQuote(input) {
164
+ const inputMint = resolveMint(input.input_mint);
165
+ const outputMint = resolveMint(input.output_mint);
166
+ const inDec = decimalsFor(inputMint);
167
+ const amountAtomic = toAtomicUnits(input.amount, inDec);
168
+ try {
169
+ const order = await ultraOrder({ inputMint, outputMint, amount: amountAtomic });
170
+ if (order.errorMessage) {
171
+ return { output: `Jupiter Ultra rejected the quote: ${order.errorMessage}`, isError: true };
172
+ }
173
+ return { output: formatQuote(order) };
174
+ }
175
+ catch (err) {
176
+ return {
177
+ output: `Jupiter Ultra /order failed: ${err instanceof Error ? err.message : String(err)}`,
178
+ isError: true,
179
+ };
180
+ }
181
+ }
182
+ async function executeJupiterSwap(input, ctx) {
183
+ const inputMint = resolveMint(input.input_mint);
184
+ const outputMint = resolveMint(input.output_mint);
185
+ const inDec = decimalsFor(inputMint);
186
+ const amountAtomic = toAtomicUnits(input.amount, inDec);
187
+ // Load wallet first — fail fast if Solana isn't set up.
188
+ let keypair;
189
+ try {
190
+ keypair = await loadSolanaKeypair();
191
+ }
192
+ catch (err) {
193
+ return {
194
+ output: `No Solana wallet found. Run \`franklin setup solana\` to generate one, or check ~/.blockrun/solana-wallet.json.\n` +
195
+ `(${err instanceof Error ? err.message : 'unknown error'})`,
196
+ isError: true,
197
+ };
198
+ }
199
+ // Step 1 — fetch order with our referral identity attached.
200
+ let order;
201
+ try {
202
+ order = await ultraOrder({
203
+ inputMint,
204
+ outputMint,
205
+ amount: amountAtomic,
206
+ taker: keypair.publicKey.toBase58(),
207
+ });
208
+ }
209
+ catch (err) {
210
+ return {
211
+ output: `Jupiter Ultra /order failed: ${err instanceof Error ? err.message : String(err)}`,
212
+ isError: true,
213
+ };
214
+ }
215
+ if (order.errorMessage || !order.transaction) {
216
+ return {
217
+ output: `Jupiter Ultra rejected the order: ${order.errorMessage ?? 'no transaction returned'}`,
218
+ isError: true,
219
+ };
220
+ }
221
+ // Step 2 — confirm with user (unless explicit auto_approve override).
222
+ if (!input.auto_approve && ctx.onAskUser) {
223
+ const quoteText = formatQuote(order);
224
+ const question = `Execute this Jupiter swap?\n\n${quoteText}\n\nWallet: ${keypair.publicKey.toBase58()}`;
225
+ const answer = await ctx.onAskUser(question, ['Confirm', 'Cancel']);
226
+ if (answer.toLowerCase() !== 'confirm') {
227
+ return { output: 'Swap cancelled by user.' };
228
+ }
229
+ }
230
+ // Step 3 — sign locally with the user's Solana keypair.
231
+ let signedBase64;
232
+ try {
233
+ const txBytes = Buffer.from(order.transaction, 'base64');
234
+ const tx = VersionedTransaction.deserialize(txBytes);
235
+ tx.sign([keypair]);
236
+ signedBase64 = Buffer.from(tx.serialize()).toString('base64');
237
+ }
238
+ catch (err) {
239
+ return {
240
+ output: `Failed to sign Jupiter transaction: ${err instanceof Error ? err.message : String(err)}`,
241
+ isError: true,
242
+ };
243
+ }
244
+ // Step 4 — submit through Jupiter Ultra (handles broadcast + confirmation).
245
+ try {
246
+ const exec = await ultraExecute({
247
+ signedTransaction: signedBase64,
248
+ requestId: order.requestId,
249
+ });
250
+ if (exec.status !== 'Success') {
251
+ return {
252
+ output: `Jupiter Ultra /execute reported ${exec.status}` +
253
+ (exec.error ? `: ${exec.error}` : '') +
254
+ (exec.code ? ` (code ${exec.code})` : ''),
255
+ isError: true,
256
+ };
257
+ }
258
+ const sig = exec.signature ?? '<unknown>';
259
+ const explorer = `https://solscan.io/tx/${sig}`;
260
+ return {
261
+ output: [
262
+ '✓ Swap executed.',
263
+ formatQuote(order),
264
+ `Signature: ${sig}`,
265
+ explorer,
266
+ ].join('\n'),
267
+ };
268
+ }
269
+ catch (err) {
270
+ return {
271
+ output: `Jupiter Ultra /execute failed: ${err instanceof Error ? err.message : String(err)}`,
272
+ isError: true,
273
+ };
274
+ }
275
+ }
276
+ // ─── Capability handlers ──────────────────────────────────────────────────
277
+ const COMMON_INPUT_PROPERTIES = {
278
+ input_mint: {
279
+ type: 'string',
280
+ description: 'Input SPL mint address, OR a symbol shortcut: SOL, USDC, USDT, JUP, BONK, WIF, TRUMP, PUMP.',
281
+ },
282
+ output_mint: {
283
+ type: 'string',
284
+ description: 'Output SPL mint address, OR a symbol shortcut (same list as input_mint).',
285
+ },
286
+ amount: {
287
+ type: 'number',
288
+ description: 'Amount of input_mint to swap, in human units (e.g. 1.5 USDC, 0.05 SOL). Decimals are looked up automatically for known mints; defaults to 9 for unknown mints.',
289
+ },
290
+ };
291
+ export const jupiterQuoteCapability = {
292
+ spec: {
293
+ name: 'JupiterQuote',
294
+ description: "Read-only price quote for a Solana DEX swap via Jupiter Ultra. Returns input/output amounts, rate, price impact, and routing path. Free — no on-chain transaction, no signing. Use this before JupiterSwap to inspect what a trade would do.",
295
+ input_schema: {
296
+ type: 'object',
297
+ required: ['input_mint', 'output_mint', 'amount'],
298
+ properties: COMMON_INPUT_PROPERTIES,
299
+ },
300
+ },
301
+ execute: async (input) => {
302
+ return executeJupiterQuote(input);
303
+ },
304
+ concurrent: true,
305
+ };
306
+ export const jupiterSwapCapability = {
307
+ spec: {
308
+ name: 'JupiterSwap',
309
+ description: "Execute a Solana DEX swap via Jupiter Ultra. Quotes the order, asks the user to confirm via AskUser, signs locally with the Franklin Solana wallet, and submits. A 20 bps platform fee is collected on-chain by Jupiter as part of the swap (BlockRun referral — official integrator program). Returns the Solscan transaction link.",
310
+ input_schema: {
311
+ type: 'object',
312
+ required: ['input_mint', 'output_mint', 'amount'],
313
+ properties: {
314
+ ...COMMON_INPUT_PROPERTIES,
315
+ auto_approve: {
316
+ type: 'boolean',
317
+ description: 'If true, skip the AskUser confirm step. Default false — agent should leave this false unless the user explicitly authorized batch execution for this turn.',
318
+ },
319
+ },
320
+ },
321
+ },
322
+ execute: async (input, ctx) => {
323
+ return executeJupiterSwap(input, ctx);
324
+ },
325
+ concurrent: false,
326
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.11.0",
3
+ "version": "3.12.1",
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": {