@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 +153 -0
- package/dist/core-DC0-qJhv.d.ts +2986 -0
- package/dist/gmx/index.d.ts +1002 -0
- package/dist/gmx/index.js +176 -0
- package/dist/gmx-DwTiknYm.js +12389 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +10081 -0
- package/package.json +52 -0
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.
|