solid-drift 0.16.0 → 0.17.0
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.
- package/README.md +35 -0
- package/dist/ai.d.ts +22 -5
- package/dist/ai.js +106 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/web3.d.ts +98 -0
- package/dist/web3.js +138 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1094,6 +1094,41 @@ const connect = createConnectButton(() => btn, { strength: 0.35 });
|
|
|
1094
1094
|
|
|
1095
1095
|
Returns `{ copyTick, chainPulse, status }`. `status()` is `"idle"`, `"ticking"` (check visible), or `"pulsing"` (ring expanding). Call `chainPulse()` after a successful connection or network switch. Under reduced motion there is no magnetic pull or scale; `copyTick()` and `chainPulse()` still show their overlays statically.
|
|
1096
1096
|
|
|
1097
|
+
### `createAgentTx(options?)`
|
|
1098
|
+
|
|
1099
|
+
AI proposes, the user approves, the transaction executes. The agent (an LLM) calls `propose()` with a plain-data proposal the user can read (`to`, `value`, `data`, `description`, `chainId`); the user calls `approve()` or `reject()`; `execute()` hands the approved proposal to your wallet adapter and the inner `createTxLifecycle` tracks signing to confirmation. The library never signs: `execute` is your wagmi/viem send function.
|
|
1100
|
+
|
|
1101
|
+
States flow `idle` to `proposed` to `approved` to `executing` to `confirmed`, with `rejected` and `failed` as the off-ramps. Invalid transitions are no-ops, so an LLM-driven UI cannot skip the user's approval. `progress()` is spring-smoothed across the whole flow for progress UI, and `tx` exposes the inner lifecycle for manual driving or extra rendering.
|
|
1102
|
+
|
|
1103
|
+
```tsx
|
|
1104
|
+
const agentTx = createAgentTx({
|
|
1105
|
+
execute: async (p) => sendTransaction({ to: p.to, value: p.value }),
|
|
1106
|
+
source: () => receiptQuery(), // wagmi/viem-style status
|
|
1107
|
+
});
|
|
1108
|
+
// The AI proposes:
|
|
1109
|
+
agentTx.propose({
|
|
1110
|
+
to: "0x…",
|
|
1111
|
+
value: "1000000000000000000",
|
|
1112
|
+
description: "Swap 1 ETH for USDC at the current rate.",
|
|
1113
|
+
});
|
|
1114
|
+
// The user reviews agentTx.proposal() and taps approve:
|
|
1115
|
+
agentTx.approve();
|
|
1116
|
+
await agentTx.execute(); // "executing" to "confirmed"
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
DriftSpec gains two LLM-generatable web3 steps for full dApp choreography: `"agentTx"` (options `to`, `description`, `value`, `data`, `chainId`, `autoApprove`) proposes a transaction mid-spec and waits for the host, via the new `createSpecPlayer(spec, refs, hooks)` third parameter, to approve and execute it through `hooks.onAgentTxStep`; `"txReceipt"` (options `hash`, `endpoint`, `timeout`) waits for a transaction hash to mine. A typical generated ceremony reads: `streamReveal` (explain) to `agentTx` (approve and send) to `txReceipt` (confirm).
|
|
1120
|
+
|
|
1121
|
+
```json
|
|
1122
|
+
{
|
|
1123
|
+
"version": 1,
|
|
1124
|
+
"scenes": [
|
|
1125
|
+
{ "primitive": "streamReveal", "target": "explainer", "options": { "text": "The agent proposes swapping 1 ETH for USDC." } },
|
|
1126
|
+
{ "primitive": "agentTx", "options": { "to": "0x…", "description": "Swap 1 ETH for USDC.", "value": "1000000000000000000" } },
|
|
1127
|
+
{ "primitive": "txReceipt", "options": { "hash": "0x…" } }
|
|
1128
|
+
]
|
|
1129
|
+
}
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1097
1132
|
### Web3 data layer
|
|
1098
1133
|
|
|
1099
1134
|
A zero-dependency read layer for chain and market data as signals: public RPC and API endpoints over `fetch`, with user-swappable endpoints. Every network primitive shares the `{ data, error, status, retry, abort }` shape, is SSR-safe (nothing fetches on the server), and polls with error backoff. Defaults are conservative because public endpoints are rate-limited. This is read-only: transaction signing stays with wallet libraries.
|
package/dist/ai.d.ts
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
import { type Accessor } from "solid-js";
|
|
16
16
|
import { type Easing, type EasingName } from "./easing.js";
|
|
17
17
|
import { type KineticTypeFrom } from "./motion.js";
|
|
18
|
+
import { type AgentTxControls, type AgentTxProposal } from "./web3.js";
|
|
19
|
+
/** Re-exported for `SpecPlayerHooks` consumers. */
|
|
20
|
+
export type { AgentTxProposal, AgentTxControls };
|
|
18
21
|
type MaybeElement = () => Element | null | undefined;
|
|
19
22
|
export type StreamRevealStatus = "idle" | "streaming" | "done";
|
|
20
23
|
export interface StreamRevealOptions {
|
|
@@ -125,7 +128,7 @@ export interface AgentStateControls {
|
|
|
125
128
|
*/
|
|
126
129
|
export declare function createAgentState(options?: AgentStateOptions): AgentStateControls;
|
|
127
130
|
/** Primitives a drift spec can choreograph. */
|
|
128
|
-
export type DriftSpecPrimitive = "kineticType" | "camera" | "colorShift" | "transition" | "beat" | "streamReveal";
|
|
131
|
+
export type DriftSpecPrimitive = "kineticType" | "camera" | "colorShift" | "transition" | "beat" | "streamReveal" | "agentTx" | "txReceipt";
|
|
129
132
|
/** One choreographed step. */
|
|
130
133
|
export interface DriftSpecStep {
|
|
131
134
|
/** Which primitive renders this step. */
|
|
@@ -139,7 +142,9 @@ export interface DriftSpecStep {
|
|
|
139
142
|
/**
|
|
140
143
|
* Primitive options. Validated against each primitive's minimal
|
|
141
144
|
* shape. "camera" reads `keyframes`, "colorShift" reads `stops`,
|
|
142
|
-
* "streamReveal" reads `text`
|
|
145
|
+
* "streamReveal" reads `text`, "agentTx" reads `to`, `description`,
|
|
146
|
+
* `value`, `data`, `chainId` and `autoApprove`, "txReceipt" reads
|
|
147
|
+
* `hash`, `endpoint` and `timeout`.
|
|
143
148
|
*/
|
|
144
149
|
options?: Record<string, unknown>;
|
|
145
150
|
/** Step budget in milliseconds, for timed players. */
|
|
@@ -185,10 +190,23 @@ export interface SpecPlayerControls {
|
|
|
185
190
|
/** Current scene index, or -1 before the first play(). */
|
|
186
191
|
scene: Accessor<number>;
|
|
187
192
|
}
|
|
193
|
+
/** Host hooks for spec steps that need the outside world. */
|
|
194
|
+
export interface SpecPlayerHooks {
|
|
195
|
+
/**
|
|
196
|
+
* Called when an "agentTx" step proposes its transaction. Show your
|
|
197
|
+
* approval UI here and call `tx.approve()` / `tx.reject()` (then
|
|
198
|
+
* `tx.execute()`) on the controls. The step waits for a terminal
|
|
199
|
+
* state ("confirmed", "rejected", "failed"); `stop()` skips it.
|
|
200
|
+
*/
|
|
201
|
+
onAgentTxStep?: (proposal: AgentTxProposal, tx: AgentTxControls) => void;
|
|
202
|
+
}
|
|
188
203
|
/**
|
|
189
204
|
* Render a validated DriftSpec: each scene's primitive plays in
|
|
190
205
|
* order against the element refs the host supplies. `duration` on a
|
|
191
|
-
* step caps that step's budget.
|
|
206
|
+
* step caps that step's budget. Web3 steps ("agentTx", "txReceipt")
|
|
207
|
+
* choreograph on-chain actions: "agentTx" proposes a transaction and
|
|
208
|
+
* waits for the host (via `hooks.onAgentTxStep`) to approve and
|
|
209
|
+
* execute it; "txReceipt" waits for a transaction hash to mine.
|
|
192
210
|
*
|
|
193
211
|
* SSR-safe: `play()` is a no-op on the server. Under reduced motion
|
|
194
212
|
* `play()` jumps straight to the last scene (the clean final frame),
|
|
@@ -203,5 +221,4 @@ export interface SpecPlayerControls {
|
|
|
203
221
|
* await player.play()
|
|
204
222
|
* ```
|
|
205
223
|
*/
|
|
206
|
-
export declare function createSpecPlayer(spec: DriftSpec, refs: Record<string, MaybeElement
|
|
207
|
-
export {};
|
|
224
|
+
export declare function createSpecPlayer(spec: DriftSpec, refs: Record<string, MaybeElement>, hooks?: SpecPlayerHooks): SpecPlayerControls;
|
package/dist/ai.js
CHANGED
|
@@ -19,6 +19,9 @@ import { now, schedule } from "./engine.js";
|
|
|
19
19
|
import { createBeat, createCamera, createColorShift, createKineticType, createTransition, } from "./motion.js";
|
|
20
20
|
import { prefersReducedMotion } from "./reduced-motion.js";
|
|
21
21
|
import { appendUnits } from "./text.js";
|
|
22
|
+
import { createAgentTx, } from "./web3.js";
|
|
23
|
+
import { createTxReceipt } from "./web3data.js";
|
|
24
|
+
import { isAddress } from "./web3data.js";
|
|
22
25
|
function clamp01(v) {
|
|
23
26
|
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
24
27
|
}
|
|
@@ -258,6 +261,8 @@ const PRIMITIVES = [
|
|
|
258
261
|
"transition",
|
|
259
262
|
"beat",
|
|
260
263
|
"streamReveal",
|
|
264
|
+
"agentTx",
|
|
265
|
+
"txReceipt",
|
|
261
266
|
];
|
|
262
267
|
/** Primitives that render into an element and need a target key. */
|
|
263
268
|
const DOM_PRIMITIVES = [
|
|
@@ -316,6 +321,41 @@ function checkOptions(primitive, options, base) {
|
|
|
316
321
|
throw new DriftSpecError(at("text"), "expected a string");
|
|
317
322
|
}
|
|
318
323
|
break;
|
|
324
|
+
case "agentTx":
|
|
325
|
+
if (typeof options.to !== "string" || !isAddress(options.to)) {
|
|
326
|
+
throw new DriftSpecError(at("to"), "expected a valid 0x address");
|
|
327
|
+
}
|
|
328
|
+
if (typeof options.description !== "string" ||
|
|
329
|
+
options.description.length === 0) {
|
|
330
|
+
throw new DriftSpecError(at("description"), "expected a non-empty string");
|
|
331
|
+
}
|
|
332
|
+
if (options.value !== undefined && typeof options.value !== "string") {
|
|
333
|
+
throw new DriftSpecError(at("value"), "expected a string");
|
|
334
|
+
}
|
|
335
|
+
if (options.data !== undefined && typeof options.data !== "string") {
|
|
336
|
+
throw new DriftSpecError(at("data"), "expected a string");
|
|
337
|
+
}
|
|
338
|
+
if (options.chainId !== undefined) {
|
|
339
|
+
assertPositiveNumber(options.chainId, at("chainId"));
|
|
340
|
+
}
|
|
341
|
+
if (options.autoApprove !== undefined &&
|
|
342
|
+
typeof options.autoApprove !== "boolean") {
|
|
343
|
+
throw new DriftSpecError(at("autoApprove"), "expected a boolean");
|
|
344
|
+
}
|
|
345
|
+
break;
|
|
346
|
+
case "txReceipt":
|
|
347
|
+
if (typeof options.hash !== "string" ||
|
|
348
|
+
!/^0x[0-9a-fA-F]{64}$/.test(options.hash)) {
|
|
349
|
+
throw new DriftSpecError(at("hash"), "expected a 0x transaction hash");
|
|
350
|
+
}
|
|
351
|
+
if (options.endpoint !== undefined &&
|
|
352
|
+
typeof options.endpoint !== "string") {
|
|
353
|
+
throw new DriftSpecError(at("endpoint"), "expected a string");
|
|
354
|
+
}
|
|
355
|
+
if (options.timeout !== undefined) {
|
|
356
|
+
assertPositiveNumber(options.timeout, at("timeout"));
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
319
359
|
}
|
|
320
360
|
}
|
|
321
361
|
/**
|
|
@@ -478,11 +518,74 @@ const stepPlayers = {
|
|
|
478
518
|
};
|
|
479
519
|
return { promise: timer.finished.then(() => c.stop()), stop: stopAll };
|
|
480
520
|
},
|
|
521
|
+
agentTx: (_target, options, _budget, hooks) => {
|
|
522
|
+
const tx = createAgentTx();
|
|
523
|
+
const proposal = {
|
|
524
|
+
to: options.to,
|
|
525
|
+
description: options.description,
|
|
526
|
+
};
|
|
527
|
+
if (typeof options.value === "string")
|
|
528
|
+
proposal.value = options.value;
|
|
529
|
+
if (typeof options.data === "string")
|
|
530
|
+
proposal.data = options.data;
|
|
531
|
+
if (typeof options.chainId === "number") {
|
|
532
|
+
proposal.chainId = options.chainId;
|
|
533
|
+
}
|
|
534
|
+
tx.propose(proposal);
|
|
535
|
+
hooks?.onAgentTxStep?.(proposal, tx);
|
|
536
|
+
if (options.autoApprove === true)
|
|
537
|
+
tx.approve();
|
|
538
|
+
// The host approves through the hook; the step ends at a terminal
|
|
539
|
+
// state. Without a host the step simply waits until stop() skips it.
|
|
540
|
+
return whenDone(() => {
|
|
541
|
+
const s = tx.state();
|
|
542
|
+
return s === "confirmed" || s === "rejected" || s === "failed";
|
|
543
|
+
});
|
|
544
|
+
},
|
|
545
|
+
txReceipt: (_target, options) => {
|
|
546
|
+
const watcher = createTxReceipt(options.hash, {
|
|
547
|
+
endpoint: typeof options.endpoint === "string" ? options.endpoint : undefined,
|
|
548
|
+
interval: 4000,
|
|
549
|
+
});
|
|
550
|
+
const timeout = typeof options.timeout === "number" ? options.timeout : 120000;
|
|
551
|
+
let cancel = null;
|
|
552
|
+
let resolveFn;
|
|
553
|
+
const started = Date.now();
|
|
554
|
+
const promise = new Promise((resolve) => {
|
|
555
|
+
resolveFn = resolve;
|
|
556
|
+
if (watcher.mined()) {
|
|
557
|
+
resolve();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
cancel = schedule(() => {
|
|
561
|
+
if (watcher.mined() || Date.now() - started >= timeout) {
|
|
562
|
+
resolve();
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
return true;
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
const done = () => {
|
|
569
|
+
cancel?.();
|
|
570
|
+
cancel = null;
|
|
571
|
+
watcher.abort();
|
|
572
|
+
};
|
|
573
|
+
return {
|
|
574
|
+
promise: promise.then(done),
|
|
575
|
+
stop: () => {
|
|
576
|
+
done();
|
|
577
|
+
resolveFn();
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
},
|
|
481
581
|
};
|
|
482
582
|
/**
|
|
483
583
|
* Render a validated DriftSpec: each scene's primitive plays in
|
|
484
584
|
* order against the element refs the host supplies. `duration` on a
|
|
485
|
-
* step caps that step's budget.
|
|
585
|
+
* step caps that step's budget. Web3 steps ("agentTx", "txReceipt")
|
|
586
|
+
* choreograph on-chain actions: "agentTx" proposes a transaction and
|
|
587
|
+
* waits for the host (via `hooks.onAgentTxStep`) to approve and
|
|
588
|
+
* execute it; "txReceipt" waits for a transaction hash to mine.
|
|
486
589
|
*
|
|
487
590
|
* SSR-safe: `play()` is a no-op on the server. Under reduced motion
|
|
488
591
|
* `play()` jumps straight to the last scene (the clean final frame),
|
|
@@ -497,7 +600,7 @@ const stepPlayers = {
|
|
|
497
600
|
* await player.play()
|
|
498
601
|
* ```
|
|
499
602
|
*/
|
|
500
|
-
export function createSpecPlayer(spec, refs) {
|
|
603
|
+
export function createSpecPlayer(spec, refs, hooks) {
|
|
501
604
|
const [status, setStatus] = createSignal("idle");
|
|
502
605
|
const [scene, setScene] = createSignal(-1);
|
|
503
606
|
let currentStop = null;
|
|
@@ -505,7 +608,7 @@ export function createSpecPlayer(spec, refs) {
|
|
|
505
608
|
let stopped = false;
|
|
506
609
|
const runStep = async (step) => {
|
|
507
610
|
const target = step.target === undefined ? undefined : refs[step.target];
|
|
508
|
-
const handle = stepPlayers[step.primitive](target, step.options ?? {}, step.duration);
|
|
611
|
+
const handle = stepPlayers[step.primitive](target, step.options ?? {}, step.duration, hooks);
|
|
509
612
|
currentStop = handle.stop;
|
|
510
613
|
if (step.duration === undefined) {
|
|
511
614
|
await handle.promise;
|
package/dist/index.d.ts
CHANGED
|
@@ -31,8 +31,8 @@ export { createKineticType, createScenePlayer, createShowreel, createCamera, cre
|
|
|
31
31
|
export type { KineticTypeFrom, KineticTypeOptions, KineticTypeStatus, KineticTypeControls, MotionScene, ScenePlayerStatus, ScenePlayerControls, ShowreelScene, ShowreelSceneKind, CameraKeyframe, CameraOptions, ColorShiftOptions, ColorShiftStatus, ColorShiftControls, TransitionType, TransitionDirection, TransitionOptions, TransitionLayerStyle, TransitionStatus, TransitionControls, BeatOptions, BeatStatus, BeatControls, BeatCutOptions, } from "./motion.js";
|
|
32
32
|
export { createDrag, type DragStatus, type DragAxis, type DragConstraints, type DragEndInfo, type DragOptions, type DragControls, } from "./gesture.js";
|
|
33
33
|
export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
|
|
34
|
-
export type { StreamRevealStatus, StreamRevealOptions, StreamRevealControls, AgentState, AgentStateTransition, AgentStateOptions, AgentStateControls, DriftSpecPrimitive, DriftSpecStep, DriftSpec, SpecPlayerStatus, SpecPlayerControls, } from "./ai.js";
|
|
35
|
-
export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, } from "./web3.js";
|
|
36
|
-
export type { TxState, TxStatusInput, TxLifecycleOptions, TxLifecycleControls, TickerOptions, TickerControls, MintRevealStatus, MintRevealOptions, MintRevealControls, ConnectButtonOptions, ConnectButtonStatus, ConnectButtonControls, } from "./web3.js";
|
|
34
|
+
export type { StreamRevealStatus, StreamRevealOptions, StreamRevealControls, AgentState, AgentStateTransition, AgentStateOptions, AgentStateControls, DriftSpecPrimitive, DriftSpecStep, DriftSpec, SpecPlayerStatus, SpecPlayerHooks, SpecPlayerControls, } from "./ai.js";
|
|
35
|
+
export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, createAgentTx, } from "./web3.js";
|
|
36
|
+
export type { TxState, TxStatusInput, TxLifecycleOptions, TxLifecycleControls, TickerOptions, TickerControls, MintRevealStatus, MintRevealOptions, MintRevealControls, ConnectButtonOptions, ConnectButtonStatus, ConnectButtonControls, AgentTxState, AgentTxProposal, AgentTxOptions, AgentTxControls, } from "./web3.js";
|
|
37
37
|
export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
|
|
38
38
|
export type { PollStatus, PollOptions, PollControls, ChainInfo, TokenPrice, TokenPriceOptions, PriceChangeOptions, GasPriceOptions, GasPriceData, BalanceOptions, BalanceData, TxReceiptData, TxReceiptOptions, BlockNumberOptions, ChainlinkPriceOptions, NFTMetadata, NFTMetadataOptions, ENSOptions, IdenticonOptions, } from "./web3data.js";
|
package/dist/index.js
CHANGED
|
@@ -30,5 +30,5 @@ export { easings, cubicBezier, linear, easeInQuad, easeOutQuad, easeInOutQuad, e
|
|
|
30
30
|
export { createKineticType, createScenePlayer, createShowreel, createCamera, createColorShift, createTransition, createBeat, createBeatCuts, } from "./motion.js";
|
|
31
31
|
export { createDrag, } from "./gesture.js";
|
|
32
32
|
export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
|
|
33
|
-
export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, } from "./web3.js";
|
|
33
|
+
export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, createAgentTx, } from "./web3.js";
|
|
34
34
|
export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
|
package/dist/web3.d.ts
CHANGED
|
@@ -82,6 +82,104 @@ export interface TxLifecycleControls {
|
|
|
82
82
|
* changes apply instantly and `progress()` jumps to its target.
|
|
83
83
|
*/
|
|
84
84
|
export declare function createTxLifecycle(options?: TxLifecycleOptions): TxLifecycleControls;
|
|
85
|
+
/**
|
|
86
|
+
* Stages of an AI-proposed transaction: the agent proposes, the user
|
|
87
|
+
* approves or rejects, the transaction executes.
|
|
88
|
+
*/
|
|
89
|
+
export type AgentTxState = "idle" | "proposed" | "approved" | "executing" | "confirmed" | "rejected" | "failed";
|
|
90
|
+
/** An AI-proposed on-chain action, in plain data the user can review. */
|
|
91
|
+
export interface AgentTxProposal {
|
|
92
|
+
/** Destination address. */
|
|
93
|
+
to: string;
|
|
94
|
+
/** Wei value as a decimal string. Default "0". */
|
|
95
|
+
value?: string;
|
|
96
|
+
/** Hex calldata. */
|
|
97
|
+
data?: string;
|
|
98
|
+
/** The AI's plain-language explanation of what this does. */
|
|
99
|
+
description: string;
|
|
100
|
+
/** Chain id. Default 1. */
|
|
101
|
+
chainId?: number;
|
|
102
|
+
}
|
|
103
|
+
export interface AgentTxOptions {
|
|
104
|
+
/**
|
|
105
|
+
* Executes the approved proposal against the wallet/chain adapter
|
|
106
|
+
* and resolves with the transaction hash. Throw to fail. Omit to
|
|
107
|
+
* drive the inner lifecycle manually through `tx`.
|
|
108
|
+
*/
|
|
109
|
+
execute?: (proposal: AgentTxProposal, signal: AbortSignal) => Promise<string>;
|
|
110
|
+
/** wagmi/viem-style status accessor, fed to the inner lifecycle. */
|
|
111
|
+
source?: Accessor<TxStatusInput>;
|
|
112
|
+
/** Confirmations that promote "pending" to "confirming". Default 1. */
|
|
113
|
+
requiredConfirmations?: number;
|
|
114
|
+
/** Spring stiffness for the progress value. Default 170. */
|
|
115
|
+
stiffness?: number;
|
|
116
|
+
/** Spring damping for the progress value. Default 26. */
|
|
117
|
+
damping?: number;
|
|
118
|
+
/** Called after entering a state, with the previous state. */
|
|
119
|
+
onEnter?: (state: AgentTxState, prev: AgentTxState) => void;
|
|
120
|
+
}
|
|
121
|
+
export interface AgentTxControls {
|
|
122
|
+
/** Current agent-tx state. */
|
|
123
|
+
state: Accessor<AgentTxState>;
|
|
124
|
+
/** The proposal under review, if any. */
|
|
125
|
+
proposal: Accessor<AgentTxProposal | undefined>;
|
|
126
|
+
/**
|
|
127
|
+
* Inner transaction lifecycle, live during "executing". Feed it
|
|
128
|
+
* through `source` or drive it manually with `tx.set()`.
|
|
129
|
+
*/
|
|
130
|
+
tx: TxLifecycleControls;
|
|
131
|
+
/** Propose a transaction for the user to review. */
|
|
132
|
+
propose: (proposal: AgentTxProposal) => void;
|
|
133
|
+
/** Approve the proposal; only from "proposed". */
|
|
134
|
+
approve: () => void;
|
|
135
|
+
/** Reject the proposal; only from "proposed". */
|
|
136
|
+
reject: () => void;
|
|
137
|
+
/**
|
|
138
|
+
* Run the approved proposal: calls `execute`, then tracks the inner
|
|
139
|
+
* lifecycle to "confirmed" or "failed". Only from "approved".
|
|
140
|
+
*/
|
|
141
|
+
execute: () => Promise<void>;
|
|
142
|
+
/** Back to "idle", aborting any in-flight execution. */
|
|
143
|
+
reset: () => void;
|
|
144
|
+
/**
|
|
145
|
+
* 0 to 1 across the whole flow, spring-smoothed for progress UI:
|
|
146
|
+
* idle 0, proposed 0.2, approved 0.35, executing 0.65,
|
|
147
|
+
* confirmed/failed 1, rejected back to 0.
|
|
148
|
+
*/
|
|
149
|
+
progress: Accessor<number>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* AI proposes, the user approves, the transaction executes.
|
|
153
|
+
*
|
|
154
|
+
* The agent (an LLM) calls `propose()` with a plain-data proposal the
|
|
155
|
+
* user can read; the user calls `approve()` or `reject()`; `execute()`
|
|
156
|
+
* hands the approved proposal to the wallet adapter and the inner
|
|
157
|
+
* `createTxLifecycle` tracks signing to confirmation. The library
|
|
158
|
+
* never signs: `execute` is your wagmi/viem send function.
|
|
159
|
+
*
|
|
160
|
+
* ```ts
|
|
161
|
+
* const agentTx = createAgentTx({
|
|
162
|
+
* execute: async (p) => sendTransaction({
|
|
163
|
+
* to: p.to, value: p.value, data: p.data,
|
|
164
|
+
* }),
|
|
165
|
+
* source: () => receiptQuery(),
|
|
166
|
+
* })
|
|
167
|
+
* // The AI proposes:
|
|
168
|
+
* agentTx.propose({
|
|
169
|
+
* to: "0x…",
|
|
170
|
+
* value: "1000000000000000000",
|
|
171
|
+
* description: "Swap 1 ETH for USDC at the current rate.",
|
|
172
|
+
* })
|
|
173
|
+
* // The user reviews proposal() and taps approve:
|
|
174
|
+
* agentTx.approve()
|
|
175
|
+
* await agentTx.execute() // "executing" to "confirmed"
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* Invalid transitions are no-ops, so LLM-driven UIs cannot skip the
|
|
179
|
+
* user's approval. SSR-safe. Under reduced motion `progress()` jumps
|
|
180
|
+
* to its target.
|
|
181
|
+
*/
|
|
182
|
+
export declare function createAgentTx(options?: AgentTxOptions): AgentTxControls;
|
|
85
183
|
export interface TickerOptions {
|
|
86
184
|
/** Decimals in the formatted output. Default 2. */
|
|
87
185
|
decimals?: number;
|
package/dist/web3.js
CHANGED
|
@@ -20,6 +20,7 @@ import { createMagnetic } from "./pointer.js";
|
|
|
20
20
|
import { prefersReducedMotion } from "./reduced-motion.js";
|
|
21
21
|
import { createSpring } from "./spring.js";
|
|
22
22
|
import { ownerDoc } from "./text.js";
|
|
23
|
+
import { isAddress } from "./web3data.js";
|
|
23
24
|
function clamp01(v) {
|
|
24
25
|
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
25
26
|
}
|
|
@@ -103,6 +104,143 @@ export function createTxLifecycle(options = {}) {
|
|
|
103
104
|
progress,
|
|
104
105
|
};
|
|
105
106
|
}
|
|
107
|
+
const AGENT_TX_PROGRESS = {
|
|
108
|
+
idle: 0,
|
|
109
|
+
proposed: 0.2,
|
|
110
|
+
approved: 0.35,
|
|
111
|
+
executing: 0.65,
|
|
112
|
+
confirmed: 1,
|
|
113
|
+
rejected: 0,
|
|
114
|
+
failed: 1,
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* AI proposes, the user approves, the transaction executes.
|
|
118
|
+
*
|
|
119
|
+
* The agent (an LLM) calls `propose()` with a plain-data proposal the
|
|
120
|
+
* user can read; the user calls `approve()` or `reject()`; `execute()`
|
|
121
|
+
* hands the approved proposal to the wallet adapter and the inner
|
|
122
|
+
* `createTxLifecycle` tracks signing to confirmation. The library
|
|
123
|
+
* never signs: `execute` is your wagmi/viem send function.
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* const agentTx = createAgentTx({
|
|
127
|
+
* execute: async (p) => sendTransaction({
|
|
128
|
+
* to: p.to, value: p.value, data: p.data,
|
|
129
|
+
* }),
|
|
130
|
+
* source: () => receiptQuery(),
|
|
131
|
+
* })
|
|
132
|
+
* // The AI proposes:
|
|
133
|
+
* agentTx.propose({
|
|
134
|
+
* to: "0x…",
|
|
135
|
+
* value: "1000000000000000000",
|
|
136
|
+
* description: "Swap 1 ETH for USDC at the current rate.",
|
|
137
|
+
* })
|
|
138
|
+
* // The user reviews proposal() and taps approve:
|
|
139
|
+
* agentTx.approve()
|
|
140
|
+
* await agentTx.execute() // "executing" to "confirmed"
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* Invalid transitions are no-ops, so LLM-driven UIs cannot skip the
|
|
144
|
+
* user's approval. SSR-safe. Under reduced motion `progress()` jumps
|
|
145
|
+
* to its target.
|
|
146
|
+
*/
|
|
147
|
+
export function createAgentTx(options = {}) {
|
|
148
|
+
const { execute, source, requiredConfirmations = 1, stiffness = 170, damping = 26, onEnter, } = options;
|
|
149
|
+
const [state, setState] = createSignal("idle");
|
|
150
|
+
const [proposal, setProposal] = createSignal(undefined);
|
|
151
|
+
const [target, setTarget] = createSignal(0);
|
|
152
|
+
const progress = createSpring(target, { stiffness, damping });
|
|
153
|
+
const tx = createTxLifecycle({
|
|
154
|
+
source,
|
|
155
|
+
requiredConfirmations,
|
|
156
|
+
stiffness,
|
|
157
|
+
damping,
|
|
158
|
+
});
|
|
159
|
+
let execAborter = null;
|
|
160
|
+
const apply = (next) => {
|
|
161
|
+
const current = state();
|
|
162
|
+
if (next === current)
|
|
163
|
+
return;
|
|
164
|
+
setState(next);
|
|
165
|
+
setTarget(AGENT_TX_PROGRESS[next]);
|
|
166
|
+
onEnter?.(next, current);
|
|
167
|
+
};
|
|
168
|
+
// Mirror the inner lifecycle into the agent flow.
|
|
169
|
+
createEffect(() => {
|
|
170
|
+
const inner = tx.state();
|
|
171
|
+
if (state() !== "executing")
|
|
172
|
+
return;
|
|
173
|
+
if (inner === "success")
|
|
174
|
+
apply("confirmed");
|
|
175
|
+
else if (inner === "failed")
|
|
176
|
+
apply("failed");
|
|
177
|
+
});
|
|
178
|
+
const propose = (p) => {
|
|
179
|
+
const s = state();
|
|
180
|
+
if (s !== "idle" && s !== "rejected" && s !== "failed" && s !== "confirmed") {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (!isAddress(p.to)) {
|
|
184
|
+
throw new Error("createAgentTx: proposal 'to' is not a valid address.");
|
|
185
|
+
}
|
|
186
|
+
if (!p.description) {
|
|
187
|
+
throw new Error("createAgentTx: proposal needs a description.");
|
|
188
|
+
}
|
|
189
|
+
setProposal(p);
|
|
190
|
+
apply("proposed");
|
|
191
|
+
};
|
|
192
|
+
const approve = () => {
|
|
193
|
+
if (state() === "proposed")
|
|
194
|
+
apply("approved");
|
|
195
|
+
};
|
|
196
|
+
const reject = () => {
|
|
197
|
+
if (state() === "proposed")
|
|
198
|
+
apply("rejected");
|
|
199
|
+
};
|
|
200
|
+
const runExecute = async () => {
|
|
201
|
+
if (state() !== "approved")
|
|
202
|
+
return;
|
|
203
|
+
const p = proposal();
|
|
204
|
+
if (!p)
|
|
205
|
+
return;
|
|
206
|
+
apply("executing");
|
|
207
|
+
tx.set("signing");
|
|
208
|
+
// Without an execute function the host drives tx manually.
|
|
209
|
+
if (!execute)
|
|
210
|
+
return;
|
|
211
|
+
execAborter?.abort();
|
|
212
|
+
execAborter = new AbortController();
|
|
213
|
+
const signal = execAborter.signal;
|
|
214
|
+
try {
|
|
215
|
+
await execute(p, signal);
|
|
216
|
+
if (!signal.aborted)
|
|
217
|
+
tx.set("pending");
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
if (!signal.aborted)
|
|
221
|
+
tx.set("failed");
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const reset = () => {
|
|
225
|
+
execAborter?.abort();
|
|
226
|
+
execAborter = null;
|
|
227
|
+
setProposal(undefined);
|
|
228
|
+
tx.reset();
|
|
229
|
+
apply("idle");
|
|
230
|
+
};
|
|
231
|
+
onCleanup(() => execAborter?.abort());
|
|
232
|
+
return {
|
|
233
|
+
state,
|
|
234
|
+
proposal,
|
|
235
|
+
tx,
|
|
236
|
+
propose,
|
|
237
|
+
approve,
|
|
238
|
+
reject,
|
|
239
|
+
execute: runExecute,
|
|
240
|
+
reset,
|
|
241
|
+
progress,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
106
244
|
/**
|
|
107
245
|
* Animated price/balance ticker: per-digit roll, direction flash.
|
|
108
246
|
*
|
package/package.json
CHANGED