@puppet.fund/operator 0.1.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 ADDED
@@ -0,0 +1,153 @@
1
+ # @puppet.fund/operator
2
+
3
+ Build an autonomous trader on [Puppet](https://puppet.fund) — a bot, an LLM agent, or a plain
4
+ cron script. The kit handles signing, fee quoting, the matchmaker connection, and venue call
5
+ shapes; you write the strategy.
6
+
7
+ You operate a **fund** — the pooled on-chain vehicle that holds the trading capital and carries
8
+ the positions — as the delegated session signer of the **account** that controls it. You open and
9
+ maintain positions. The human creates the account, allocates the fund, and redeems on the site.
10
+
11
+ ```bash
12
+ bun add @puppet.fund/operator
13
+ ```
14
+
15
+ The fastest start is the scaffolder, which sets up a project for you:
16
+
17
+ ```bash
18
+ bunx @puppet.fund/templates my-operator gmx
19
+ ```
20
+
21
+ ## Entrypoints
22
+
23
+ The package is split by operator, so you choose yours:
24
+
25
+ - **`@puppet.fund/operator/gmx`** — the ready-made GMX V2 venue (`gmxOperator(core)`), wrapping a
26
+ core you build. What the scaffolder sets up, and what most users want.
27
+ - **`@puppet.fund/operator`** — the generic, venue-agnostic core. `createOperatorCore(config)`
28
+ gives you pairing + endpoint resolution, the RPC/indexer clients, account + fund prediction and
29
+ deploy checks, the matchmaker compact, the generic `operate(callList)` dispatch,
30
+ `getFundBalance()`, and connection lifecycle (`status` / `isOpen()` / `close()`). Compose it
31
+ with your own venue call shapes to build an extended operator — a different venue, base token,
32
+ or surface. The GMX venue is built on exactly this. Also here: `runOperator`,
33
+ `pairOverBrowser`, `createCompact`, `TOKEN_ID`, and the compact error helpers. Type your
34
+ operator against `IOperatorCore`.
35
+
36
+ ## Usage
37
+
38
+ Build the **core** (the connection — pairing/endpoint resolution, signing, the matchmaker compact,
39
+ your account + fund, lifecycle), then wrap it in the GMX venue. The core does the async setup and
40
+ owns the connection; `gmxOperator(core)` is synchronous and adds only GMX execution + reads — so
41
+ you call connection/account actions on `core`, GMX actions on `gmx`. When you pair, the site hands
42
+ over its matchmaker/indexer endpoints, so a paired operator needs no URL config; RPC defaults to
43
+ the public Arbitrum endpoint (set `rpcUrl` for anything beyond a first run). GMX runs on a WETH
44
+ fund (collateral and the keeper fee are the same token), so create your account and allocate a
45
+ WETH fund on the site first.
46
+
47
+ ```ts
48
+ import { createOperatorCore, runOperator } from '@puppet.fund/operator'
49
+ import { GMX_BASE_TOKEN_ID, gmxOperator } from '@puppet.fund/operator/gmx'
50
+
51
+ // Zero-config: endpoints + session key arrive over the pairing tunnel.
52
+ const core = await createOperatorCore({ baseTokenId: GMX_BASE_TOKEN_ID })
53
+ const gmx = gmxOperator(core)
54
+
55
+ // Override endpoints by passing matchmakerUrl / indexerUrl / rpcUrl to createOperatorCore,
56
+ // or add signerKey + user (with matchmakerUrl + indexerUrl) to run headless without pairing.
57
+
58
+ const deployable = await core.getFundBalance() // core: account + connection
59
+ const positions = await gmx.getPositions() // gmx: venue reads
60
+ // decide, then act. One primitive for every order type; opening is an increase from zero,
61
+ // a full close is a decrease by the whole size (executionFee auto-quoted when omitted):
62
+ // await gmx.createOrder({ orderType: gmx.GMX_ORDER_TYPE.MarketIncrease, market, isLong, collateralDelta, sizeDeltaUsd })
63
+ // await gmx.createOrder({ orderType: gmx.GMX_ORDER_TYPE.MarketDecrease, market, isLong, collateralDelta: 0n, sizeDeltaUsd })
64
+ // collateral freed by a close returns to the fund on its own — it shows up in getFundBalance()
65
+ // and your next order re-signs it automatically (the surplus rides along as the leg's amountIn).
66
+ ```
67
+
68
+ ## The account model
69
+
70
+ Two addresses matter, both deterministic and printed at startup:
71
+
72
+ - **`core.account`** — your account, the identity that signs. The session key delegated to the
73
+ operator signs through it.
74
+ - **`core.fund`** — the fund your account controls: it holds the pooled base capital (yours plus
75
+ any subscribers'), it is the GMX account positions live under, and every `operate` executes from
76
+ it. Its idle base is `getFundBalance()` — deployable as collateral with no manual bookkeeping:
77
+ fund value moves settle as signed transfer legs, and `createOrder` declares the outflow and
78
+ re-signs anything GMX returned since the last order in the same leg. The protocol co-signs
79
+ every action against the rules the backers signed.
80
+
81
+ ## Surface
82
+
83
+ - `createOperatorCore(config)` (async, package root) — pairs / resolves endpoints, sets up the
84
+ matchmaker compact, predicts + checks the account and the fund, and returns the connection +
85
+ account surface you call directly: `getFundBalance()` (live base in the fund),
86
+ `getFundSignedBalance()` (the signed portion, on-chain), `isOpen()` (is the matchmaker
87
+ connected — gate a tick on it), `status`, `operate(callList, transferList?)` (the generic
88
+ signed-call dispatch under the venue surface — declare value the calls move as transfer legs;
89
+ the default 0/0 leg fits value-neutral dispatches, and the protocol attestor co-signs only
90
+ calls inside the venue perimeter it can screen, so arbitrary targets are rejected), and
91
+ `close()`; plus `account`, `fund`, `signer`,
92
+ `user`, `token`, `baseTokenId`, `publicClient`, `sql`, `compact`. Type it as `IOperatorCore`.
93
+ `matchmakerUrl` / `indexerUrl` are optional (a paired operator receives them from the site; pass
94
+ them to override, or supply both with `signerKey` + `user` to run headless). `rpcUrl` defaults
95
+ to the public Arbitrum endpoint. `dryRun: true` runs every dispatch through the identical
96
+ local verification + venue screen the matchmaker applies, logs what would be sent, and skips
97
+ the dispatch — the way to watch a strategy decide without risking funds. The session signer
98
+ stays private to the core — you get signed intents, not the key.
99
+ - `gmxOperator(core, opts?)` (sync, `@puppet.fund/operator/gmx`) — the GMX venue over a core. Build
100
+ the core with `baseTokenId: GMX_BASE_TOKEN_ID` (WETH) and pass it in; this validates the base token
101
+ and adds GMX-only surface. Reads: `getMarket(indexToken)` (the canonical perp for the base token;
102
+ throws only if still ambiguous), `getPositions(account?)` (defaults to your fund; pass any GMX
103
+ account to read someone else's), `getOrders(account?)`, `markets`. Writes: `createOrder(...)` —
104
+ the one primitive for every increase/decrease order type (market/limit/stop/take-profit) via
105
+ `orderType` + `triggerPrice`, `executionFee` auto-quoted when omitted, fund balance pre-checked —
106
+ `cancelOrder(key)`, `updateOrder(...)`. Plus `GMX_ORDER_TYPE`, `GMX_DECREASE_SWAP_TYPE`, and the
107
+ pure helpers `usd`, `weth`, `formatUsd`, `formatWeth`, `gmxPrice` (30dp-USD / 18dp-WETH amounts
108
+ and price units), `acceptablePrice` (slippage-bounded price, correct direction per side and
109
+ order kind), `dominantPosition` (largest position on a market), and `positionMetrics`
110
+ (size/collateral/margin incl. unrealized PnL, valuing WETH or USDC collateral correctly).
111
+ `opts.executionFeeBufferBps` (default 2000 = 20%) pads the auto-quote. It does NOT re-expose
112
+ core methods — call those on `core`.
113
+ - Lifecycle + building blocks (`runOperator`, `pairOverBrowser`, `createCompact`, `TOKEN_ID`, compact
114
+ types + error helpers) live at the package root — see **Entrypoints** above. A GMX operator is
115
+ `createOperatorCore({ baseTokenId: GMX_BASE_TOKEN_ID })` + `gmxOperator(core)`, run under
116
+ `runOperator(core, body)`.
117
+
118
+ ## Pairing
119
+
120
+ Without a `signerKey`, the core opens a one-time `127.0.0.1` listener and prints a link you
121
+ open on the site. The browser seals your session key — and the site's matchmaker/indexer
122
+ endpoints — to an ephemeral key in that link and posts it back. The key reaches the operator in
123
+ memory only, never written to disk; the endpoints come from the site you're pairing with. Your
124
+ account and fund must already exist (create the account and allocate a WETH fund on the site
125
+ first).
126
+
127
+ The browser seals the *derived* session key, never your wallet's bind signature, so a
128
+ paired operator can sign operate intents but cannot deploy accounts under you.
129
+
130
+ ## Trust and limits
131
+
132
+ The session key delegated to an operator can open and manage positions within the
133
+ rules you signed, but can never withdraw — withdrawals always return to your own
134
+ wallet, and every action is co-signed by the protocol. That key is derived
135
+ deterministically from a single wallet signature: it is the same on every machine,
136
+ and there is no on-chain revocation yet. So protecting your pairing link and the host
137
+ that runs the operator is protecting full operating authority. A leaked key cannot
138
+ move funds out, but it can churn positions and burn execution fees until you withdraw
139
+ and abandon the account. Only ever pair on the real https://puppet.fund.
140
+
141
+ ## Scope
142
+
143
+ Today this kit ships one venue — GMX V2 on Arbitrum, on a WETH fund (GMX pays its
144
+ keeper fee in WETH, so collateral and fee are the same token and nothing else needs
145
+ funding). `createOrder` stays a single primitive for every order type;
146
+ `operate(callList)` is the generic dispatch underneath it, but every call is screened
147
+ by the protocol attestor, which today co-signs only the GMX order perimeter (create,
148
+ cancel, update, and their token legs). Swaps, claims, and foreign targets are rejected
149
+ until the screen widens. `operate` is also where you set custom per-call gas (each
150
+ `callList` entry carries its own `gasLimit`). The venue-agnostic plumbing lives in
151
+ `createOperatorCore` (the package root), so more venues, base tokens, and chains can
152
+ be added as sibling `@puppet.fund/operator/<venue>` entrypoints built on the same
153
+ core — or you can build your own.