@oracle-agent/oracle 0.2.0 → 0.2.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.
@@ -0,0 +1,216 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 900" width="1400" height="900">
2
+ <defs>
3
+ <style>
4
+ @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400;1,500&amp;family=Outfit:wght@300;400;500&amp;family=IBM+Plex+Mono:wght@400;500&amp;display=swap');
5
+ </style>
6
+ <radialGradient id="bg" cx="50%" cy="35%" r="70%">
7
+ <stop offset="0%" stop-color="#111925"/>
8
+ <stop offset="45%" stop-color="#0B1018"/>
9
+ <stop offset="100%" stop-color="#070b10"/>
10
+ </radialGradient>
11
+ <radialGradient id="orb1" cx="50%" cy="50%" r="50%">
12
+ <stop offset="0%" stop-color="#7CC4FF" stop-opacity="0.18"/>
13
+ <stop offset="55%" stop-color="#7CC4FF" stop-opacity="0.04"/>
14
+ <stop offset="100%" stop-color="#7CC4FF" stop-opacity="0"/>
15
+ </radialGradient>
16
+ <radialGradient id="orb2" cx="50%" cy="50%" r="50%">
17
+ <stop offset="0%" stop-color="#50D2C1" stop-opacity="0.10"/>
18
+ <stop offset="100%" stop-color="#50D2C1" stop-opacity="0"/>
19
+ </radialGradient>
20
+ <pattern id="grid" width="48" height="48" patternUnits="userSpaceOnUse">
21
+ <path d="M 48 0 L 0 0 0 48" fill="none" stroke="rgba(124,196,255,0.06)" stroke-width="1"/>
22
+ </pattern>
23
+ <linearGradient id="panelStroke" x1="0%" y1="0%" x2="100%" y2="100%">
24
+ <stop offset="0%" stop-color="rgba(124,196,255,0.28)"/>
25
+ <stop offset="100%" stop-color="rgba(124,196,255,0.08)"/>
26
+ </linearGradient>
27
+ <linearGradient id="cardFill" x1="0%" y1="0%" x2="0%" y2="100%">
28
+ <stop offset="0%" stop-color="#152031"/>
29
+ <stop offset="100%" stop-color="#111925"/>
30
+ </linearGradient>
31
+ <filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
32
+ <feGaussianBlur stdDeviation="8" result="b"/>
33
+ <feMerge>
34
+ <feMergeNode in="b"/>
35
+ <feMergeNode in="SourceGraphic"/>
36
+ </feMerge>
37
+ </filter>
38
+ <filter id="nodeGlow" x="-40%" y="-40%" width="180%" height="180%">
39
+ <feGaussianBlur stdDeviation="4" result="b"/>
40
+ <feMerge>
41
+ <feMergeNode in="b"/>
42
+ <feMergeNode in="SourceGraphic"/>
43
+ </feMerge>
44
+ </filter>
45
+ <marker id="ah-blue" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
46
+ <path d="M0,0 L8,4 L0,8 Z" fill="#7CC4FF"/>
47
+ </marker>
48
+ <marker id="ah-poly" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
49
+ <path d="M0,0 L8,4 L0,8 Z" fill="#3B82F6"/>
50
+ </marker>
51
+ <marker id="ah-hl" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
52
+ <path d="M0,0 L8,4 L0,8 Z" fill="#50D2C1"/>
53
+ </marker>
54
+ <marker id="ah-rh" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
55
+ <path d="M0,0 L8,4 L0,8 Z" fill="#d7ff4d"/>
56
+ </marker>
57
+ <marker id="ah-sol" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
58
+ <path d="M0,0 L8,4 L0,8 Z" fill="#9945FF"/>
59
+ </marker>
60
+ <marker id="ah-btc" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
61
+ <path d="M0,0 L8,4 L0,8 Z" fill="#F0B90B"/>
62
+ </marker>
63
+ <marker id="ah-mute" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto">
64
+ <path d="M0,0 L8,4 L0,8 Z" fill="#5B738F"/>
65
+ </marker>
66
+ </defs>
67
+
68
+ <!-- page bg -->
69
+ <rect width="1400" height="900" fill="url(#bg)"/>
70
+ <rect width="1400" height="900" fill="url(#grid)"/>
71
+ <ellipse cx="700" cy="420" rx="520" ry="280" fill="url(#orb1)"/>
72
+ <ellipse cx="980" cy="620" rx="280" ry="180" fill="url(#orb2)"/>
73
+
74
+ <!-- ========== brand top-left (oracle) ========== -->
75
+ <g id="brand">
76
+ <circle cx="52" cy="48" r="7" fill="#7CC4FF" filter="url(#softGlow)"/>
77
+ <circle cx="52" cy="48" r="3.2" fill="#0B1018"/>
78
+ <text x="70" y="54" fill="#E8F1FA" font-family="Outfit, system-ui, sans-serif" font-size="26" font-weight="400" letter-spacing="-0.3">oracle</text>
79
+ <text x="52" y="78" fill="#5B738F" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" letter-spacing="1.8">MULTI-PROFILE MESH · POWERED BY HERMES</text>
80
+ </g>
81
+
82
+ <!-- right meta -->
83
+ <text x="1350" y="48" fill="#5B738F" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" text-anchor="end" letter-spacing="1.5">FOR USERS</text>
84
+ <text x="1350" y="68" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="13" text-anchor="end">graph · customizable · add agents</text>
85
+
86
+ <!-- ========== main glass panel ========== -->
87
+ <rect x="48" y="110" width="1304" height="730" rx="22" fill="#111925" fill-opacity="0.72" stroke="url(#panelStroke)" stroke-width="1.2"/>
88
+ <!-- panel header bar -->
89
+ <rect x="48" y="110" width="1304" height="52" rx="22" fill="#152031" fill-opacity="0.9"/>
90
+ <rect x="48" y="140" width="1304" height="22" fill="#152031" fill-opacity="0.9"/>
91
+ <text x="72" y="142" fill="#7CC4FF" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="11" letter-spacing="1.6">FOR USERS</text>
92
+ <text x="700" y="142" fill="#9BB6D1" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="11" text-anchor="middle" letter-spacing="1.6">MULTI-PROFILE // CUSTOMIZABLE</text>
93
+ <text x="1328" y="142" fill="#5B738F" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="11" text-anchor="end" letter-spacing="1.4">POWERED BY HERMES</text>
94
+
95
+ <!-- pills -->
96
+ <g font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" letter-spacing="1.2">
97
+ <rect x="72" y="182" width="128" height="28" rx="14" fill="none" stroke="rgba(124,196,255,0.28)" stroke-width="1"/>
98
+ <text x="136" y="200" fill="#A7D8FF" text-anchor="middle">MULTI PROFILES</text>
99
+ <rect x="210" y="182" width="128" height="28" rx="14" fill="none" stroke="rgba(124,196,255,0.28)" stroke-width="1"/>
100
+ <text x="274" y="200" fill="#A7D8FF" text-anchor="middle">CUSTOMIZABLE</text>
101
+ <rect x="348" y="182" width="112" height="28" rx="14" fill="none" stroke="rgba(124,196,255,0.28)" stroke-width="1"/>
102
+ <text x="404" y="200" fill="#A7D8FF" text-anchor="middle">ADD AGENTS</text>
103
+ <rect x="470" y="182" width="118" height="28" rx="14" fill="none" stroke="rgba(124,196,255,0.28)" stroke-width="1"/>
104
+ <text x="529" y="200" fill="#A7D8FF" text-anchor="middle">MORE CHAINS</text>
105
+ <rect x="598" y="182" width="148" height="28" rx="14" fill="none" stroke="rgba(124,196,255,0.28)" stroke-width="1"/>
106
+ <text x="672" y="200" fill="#A7D8FF" text-anchor="middle">POWERED BY HERMES</text>
107
+ </g>
108
+
109
+ <!-- ========== connectors (behind nodes) ========== -->
110
+ <!-- input -> oracle -->
111
+ <line x1="700" y1="310" x2="700" y2="360" stroke="#7CC4FF" stroke-width="2.5" marker-end="url(#ah-blue)" filter="url(#nodeGlow)"/>
112
+ <line x1="700" y1="310" x2="700" y2="360" stroke="#7CC4FF" stroke-width="10" stroke-opacity="0.12"/>
113
+
114
+ <!-- oracle hub fan-out to profiles -->
115
+ <!-- poly left -->
116
+ <path d="M620 450 C560 450 420 450 280 520" fill="none" stroke="#3B82F6" stroke-width="2" marker-end="url(#ah-poly)"/>
117
+ <path d="M620 450 C560 450 420 450 280 520" fill="none" stroke="#3B82F6" stroke-width="8" stroke-opacity="0.10"/>
118
+ <!-- hl center-left -->
119
+ <path d="M660 470 C640 500 560 520 500 560" fill="none" stroke="#50D2C1" stroke-width="2" marker-end="url(#ah-hl)"/>
120
+ <path d="M660 470 C640 500 560 520 500 560" fill="none" stroke="#50D2C1" stroke-width="8" stroke-opacity="0.10"/>
121
+ <!-- rh center -->
122
+ <line x1="700" y1="470" x2="700" y2="560" stroke="#d7ff4d" stroke-width="2" marker-end="url(#ah-rh)"/>
123
+ <line x1="700" y1="470" x2="700" y2="560" stroke="#d7ff4d" stroke-width="8" stroke-opacity="0.10"/>
124
+ <!-- sol center-right -->
125
+ <path d="M740 470 C760 500 840 520 900 560" fill="none" stroke="#9945FF" stroke-width="2" marker-end="url(#ah-sol)"/>
126
+ <path d="M740 470 C760 500 840 520 900 560" fill="none" stroke="#9945FF" stroke-width="8" stroke-opacity="0.10"/>
127
+ <!-- btc right -->
128
+ <path d="M780 450 C860 450 1000 450 1120 520" fill="none" stroke="#F0B90B" stroke-width="2" marker-end="url(#ah-btc)"/>
129
+ <path d="M780 450 C860 450 1000 450 1120 520" fill="none" stroke="#F0B90B" stroke-width="8" stroke-opacity="0.10"/>
130
+ <!-- builder bottom -->
131
+ <path d="M700 470 C700 510 700 640 700 680" fill="none" stroke="#5B738F" stroke-width="1.5" stroke-dasharray="4 4" marker-end="url(#ah-mute)"/>
132
+
133
+ <!-- ========== 01 INPUT / COMMAND ========== -->
134
+ <g filter="url(#nodeGlow)">
135
+ <rect x="575" y="230" width="250" height="80" rx="14" fill="url(#cardFill)" stroke="rgba(124,196,255,0.35)" stroke-width="1.2"/>
136
+ <text x="700" y="258" fill="#5B738F" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" text-anchor="middle" letter-spacing="1.6">YOU</text>
137
+ <text x="700" y="282" fill="#E8F1FA" font-family="Outfit, system-ui, sans-serif" font-size="18" font-weight="500" text-anchor="middle">Input / Command</text>
138
+ <text x="700" y="300" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300" text-anchor="middle">what you type or ask</text>
139
+ </g>
140
+
141
+ <!-- ========== 02 CONTROL PLANE · oracle ========== -->
142
+ <g filter="url(#nodeGlow)">
143
+ <rect x="520" y="360" width="360" height="110" rx="16" fill="url(#cardFill)" stroke="rgba(124,196,255,0.45)" stroke-width="1.4"/>
144
+ <rect x="520" y="360" width="360" height="110" rx="16" fill="rgba(124,196,255,0.04)"/>
145
+ <text x="700" y="392" fill="#7CC4FF" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" text-anchor="middle" letter-spacing="1.8">CONTROL PLANE</text>
146
+ <text x="700" y="424" fill="#E8F1FA" font-family="Cormorant Garamond, Georgia, serif" font-size="34" font-weight="500" font-style="italic" text-anchor="middle">oracle</text>
147
+ <text x="700" y="450" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300" text-anchor="middle">route · bound · evolve</text>
148
+ </g>
149
+
150
+ <!-- ========== 03 PROFILE CARDS ========== -->
151
+ <!-- polymarket -->
152
+ <g>
153
+ <rect x="80" y="520" width="260" height="130" rx="16" fill="#111925" stroke="rgba(59,130,246,0.45)" stroke-width="1.2"/>
154
+ <rect x="80" y="520" width="260" height="130" rx="16" fill="rgba(59,130,246,0.06)"/>
155
+ <text x="100" y="548" fill="#3B82F6" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" letter-spacing="1.6">PROFILE</text>
156
+ <text x="100" y="578" fill="#A7D8FF" font-family="Outfit, system-ui, sans-serif" font-size="18" font-weight="500">polymarket agent</text>
157
+ <text x="100" y="600" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300">events · arb · markets</text>
158
+ <rect x="100" y="618" width="100" height="20" rx="10" fill="none" stroke="rgba(59,130,246,0.35)"/>
159
+ <text x="150" y="632" fill="#7CC4FF" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="9" text-anchor="middle">Polymarket</text>
160
+ </g>
161
+
162
+ <!-- hyperliquid -->
163
+ <g>
164
+ <rect x="370" y="560" width="260" height="130" rx="16" fill="#111925" stroke="rgba(80,210,193,0.45)" stroke-width="1.2"/>
165
+ <rect x="370" y="560" width="260" height="130" rx="16" fill="rgba(80,210,193,0.06)"/>
166
+ <text x="390" y="588" fill="#50D2C1" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" letter-spacing="1.6">PROFILE</text>
167
+ <text x="390" y="618" fill="#A8F0E6" font-family="Outfit, system-ui, sans-serif" font-size="18" font-weight="500">hyperliquid agent</text>
168
+ <text x="390" y="640" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300">perps · hip-3/4 · ta</text>
169
+ <rect x="390" y="658" width="170" height="20" rx="10" fill="none" stroke="rgba(80,210,193,0.35)"/>
170
+ <text x="475" y="672" fill="#50D2C1" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="9" text-anchor="middle">Hyperliquid · HyperEVM 999</text>
171
+ </g>
172
+
173
+ <!-- robinhood -->
174
+ <g>
175
+ <rect x="660" y="560" width="260" height="130" rx="16" fill="#111925" stroke="rgba(215,255,77,0.40)" stroke-width="1.2"/>
176
+ <rect x="660" y="560" width="260" height="130" rx="16" fill="rgba(215,255,77,0.05)"/>
177
+ <text x="680" y="588" fill="#d7ff4d" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" letter-spacing="1.6">PROFILE</text>
178
+ <text x="680" y="618" fill="#EAF7B0" font-family="Outfit, system-ui, sans-serif" font-size="18" font-weight="500">robinhood agent</text>
179
+ <text x="680" y="640" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300">markets · nfts · names</text>
180
+ <rect x="680" y="658" width="150" height="20" rx="10" fill="none" stroke="rgba(215,255,77,0.35)"/>
181
+ <text x="755" y="672" fill="#d7ff4d" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="9" text-anchor="middle">Robinhood Chain 4663</text>
182
+ </g>
183
+
184
+ <!-- solana -->
185
+ <g>
186
+ <rect x="950" y="560" width="200" height="130" rx="16" fill="#111925" stroke="rgba(153,69,255,0.45)" stroke-width="1.2"/>
187
+ <rect x="950" y="560" width="200" height="130" rx="16" fill="rgba(153,69,255,0.06)"/>
188
+ <text x="970" y="588" fill="#9945FF" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" letter-spacing="1.6">PROFILE</text>
189
+ <text x="970" y="618" fill="#D4B3FF" font-family="Outfit, system-ui, sans-serif" font-size="18" font-weight="500">solana agent</text>
190
+ <text x="970" y="640" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300">jup · nfts · dexs</text>
191
+ <rect x="970" y="658" width="70" height="20" rx="10" fill="none" stroke="rgba(153,69,255,0.35)"/>
192
+ <text x="1005" y="672" fill="#c4a0ff" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="9" text-anchor="middle">Solana</text>
193
+ </g>
194
+
195
+ <!-- bitcoin (top-right of profile row) -->
196
+ <g>
197
+ <rect x="1160" y="520" width="160" height="130" rx="16" fill="#111925" stroke="rgba(240,185,11,0.40)" stroke-width="1.2"/>
198
+ <rect x="1160" y="520" width="160" height="130" rx="16" fill="rgba(240,185,11,0.05)"/>
199
+ <text x="1180" y="548" fill="#F0B90B" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" letter-spacing="1.6">PROFILE</text>
200
+ <text x="1180" y="578" fill="#F8E29A" font-family="Outfit, system-ui, sans-serif" font-size="18" font-weight="500">bitcoin</text>
201
+ <text x="1180" y="600" fill="#9BB6D1" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300">ord · runes</text>
202
+ <rect x="1180" y="618" width="50" height="20" rx="10" fill="none" stroke="rgba(240,185,11,0.35)"/>
203
+ <text x="1205" y="632" fill="#F0B90B" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="9" text-anchor="middle">BTC</text>
204
+ </g>
205
+
206
+ <!-- protocol builder -->
207
+ <g>
208
+ <rect x="520" y="710" width="360" height="72" rx="14" fill="#111925" stroke="rgba(124,196,255,0.22)" stroke-width="1" stroke-dasharray="5 4"/>
209
+ <text x="700" y="738" fill="#5B738F" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" text-anchor="middle" letter-spacing="1.6">BUILDER</text>
210
+ <text x="700" y="762" fill="#E8F1FA" font-family="Outfit, system-ui, sans-serif" font-size="16" font-weight="400" text-anchor="middle">protocol builder · gated templates · user-signed deploy</text>
211
+ </g>
212
+
213
+ <!-- footer -->
214
+ <text x="72" y="870" fill="#5B738F" font-family="Outfit, system-ui, sans-serif" font-size="12" font-weight="300">oracle routes input/command across a live multi-profile desk. profiles evolve. chains get added. not one bot.</text>
215
+ <text x="1328" y="870" fill="#3a6f99" font-family="IBM Plex Mono, ui-monospace, monospace" font-size="10" text-anchor="end">self-custody · prepare → local sign</text>
216
+ </svg>
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { listProtocolTemplates, runProtocolTemplateGate } from "../src/protocol-templates/gate.mjs";
3
+
4
+ const id = process.argv[2];
5
+ if (!id || id === "list") {
6
+ console.log(JSON.stringify(listProtocolTemplates(), null, 2));
7
+ process.exit(0);
8
+ }
9
+ try {
10
+ const r = runProtocolTemplateGate(id, { allowSkipStatic: process.env.REQUIRE_SLITHER !== "1" });
11
+ console.log(JSON.stringify(r, null, 2));
12
+ } catch (e) {
13
+ console.error(String(e.message || e));
14
+ process.exit(1);
15
+ }
@@ -1,38 +1,59 @@
1
1
  ---
2
2
  name: oracle-protocol-builder
3
- description: Use when the user wants Oracle to act as a builder: scaffold contracts/apps, prepare unsigned deploys, and create launch runbooks.
3
+ description: Use when the user wants Oracle to scaffold/deploy protocol templates. Gated Foundry templates; prepare-only deploy; not a firm audit.
4
4
  ---
5
5
 
6
6
  # Protocol builder
7
7
 
8
- Oracle can be a builder, not only a trader/analyzer. The boundary stays the same:
9
- it writes and prepares; the user signs and deploys.
8
+ Oracle scaffolds and **prepares unsigned deploys**. The user signs. Custody boundary unchanged.
10
9
 
11
- ## Builder modes
10
+ ## Security gate (v1)
12
11
 
13
- - scaffold contracts and tests
14
- - scaffold mint/gacha/claim pages
15
- - build DEX/pool/router integrations
16
- - write deploy and verify scripts
17
- - produce unsigned deploy or admin transactions
18
- - create launch runbooks and monitoring dashboards
19
- - audit authority, upgradeability, fees, and emergency controls
12
+ Before any template deploy prepare:
20
13
 
21
- ## Required order
14
+ 1. `forge test` must pass on the template
15
+ 2. Slither runs when installed (optional skip if missing; set `REQUIRE_SLITHER=1` to hard-require)
16
+ 3. JS `runProtocolTemplateGate` / `prepareTemplateDeploy` refuse otherwise
22
17
 
23
- 1. Authority model: owner, admin, upgrader, pauser, treasury, withdrawer.
24
- 2. Threat model: what can go wrong and who can rug or halt.
25
- 3. Tests: caps, payments, roles, upgrades, withdrawals, randomness, routing.
26
- 4. Simulation: deploy, initialize, and first user action.
27
- 5. Prepare: unsigned artifact only, bound to chain and destination.
28
- 6. Receipt: deployed address, source verification, event logs, and state readback.
18
+ ```js
19
+ // list
20
+ data.call("protocol-templates", "list")
21
+ // gate
22
+ data.call("protocol-templates", "gate", { templateId: "safe-erc20" })
23
+ // unsigned deploy prepare (stamped)
24
+ data.call("protocol-templates", "prepareDeploy", {
25
+ templateId: "safe-erc20",
26
+ chainId: 8453,
27
+ args: [name, symbol, supply, initialHolder, initialOwner],
28
+ })
29
+ ```
29
30
 
30
- ## Refusal line
31
+ CLI: `npm run protocol:gate -- safe-erc20`
32
+
33
+ ## Shipped template: `safe-erc20`
34
+
35
+ - Fixed supply, mint once in constructor (no hidden mint)
36
+ - Ownable2Step + pause
37
+ - No tax / blacklist / max-tx / upgrade proxy
38
+ - Foundry tests included
39
+
40
+ ## Honesty
31
41
 
32
- Refuse hidden drains, honeypots, wash-trading systems, undisclosed tax switches,
33
- fake TVL, or any contract whose main purpose is deceiving buyers.
42
+ **Reviewed templates + automated tests paid Solidity audit.**
34
43
 
35
- ## Output standard
44
+ Docs and prepare envelopes always set `firmAudit: false` and carry the disclaimer.
45
+ Mainnet TVL → independent firm audit.
46
+
47
+ ## Required order (custom work)
48
+
49
+ 1. Authority model
50
+ 2. Threat model
51
+ 3. Tests (Foundry)
52
+ 4. Static analysis when available
53
+ 5. Gate green
54
+ 6. Prepare unsigned only
55
+ 7. User signs + verify source
56
+
57
+ ## Refusal line
36
58
 
37
- Lead with the authority model and irreversible choices. Then the file plan or
38
- artifact. End with the exact tests/simulations run and what remains unverified.
59
+ Refuse hidden drains, honeypots, wash-trading systems, undisclosed tax switches, fake TVL, or any contract whose main purpose is deceiving buyers.
@@ -59,6 +59,15 @@ registerProvider({
59
59
  description: "Hyperliquid public /info",
60
60
  });
61
61
 
62
+ registerProvider({
63
+ id: "protocol-templates",
64
+ venue: "protocol-builder",
65
+ chainIds: [],
66
+ auth: "none",
67
+ ops: ["health", "list", "gate", "prepareDeploy"],
68
+ description: "Gated Safe* Solidity templates: forge test required before unsigned deploy prepare. NOT a firm audit.",
69
+ });
70
+
62
71
  registerProvider({
63
72
  id: "poly-public",
64
73
  venue: "poly",
@@ -29,6 +29,8 @@ import * as hlstake from "./providers/hl-staking.mjs";
29
29
  import * as hlmarkets from "./providers/hl-markets.mjs";
30
30
  import * as hloutcome from "./providers/hl-outcome.mjs";
31
31
  import * as polyclob from "./providers/poly-clob.mjs";
32
+ import * as protoTpl from "../protocol-templates/prepare-deploy.mjs";
33
+ import { listProtocolTemplates, runProtocolTemplateGate } from "../protocol-templates/gate.mjs";
32
34
  import * as hlperps from "./providers/hl-perps.mjs";
33
35
  import * as gecko from "./providers/geckoterminal.mjs";
34
36
  import * as curve from "./providers/curve.mjs";
@@ -218,6 +220,12 @@ const OPS = {
218
220
  prepareOrder: (o, a = {}) => hloutcome.hlPrepareOutcomeOrder(a, o),
219
221
  prepareCancel: (o, a = {}) => hloutcome.hlPrepareOutcomeCancel(a, o),
220
222
  },
223
+ "protocol-templates": {
224
+ health: () => ({ ok: true, templates: listProtocolTemplates().map((t) => t.id), firmAudit: false }),
225
+ list: () => listProtocolTemplates(),
226
+ gate: (o, a = {}) => runProtocolTemplateGate(a.templateId || a.id, o),
227
+ prepareDeploy: (o, a = {}) => protoTpl.prepareTemplateDeploy(a, o),
228
+ },
221
229
  "poly-clob": {
222
230
  prepareOrder: (o, a = {}) => {
223
231
  // prepare is sync; wrap for dataCall
@@ -0,0 +1,179 @@
1
+ // Protocol template security gate.
2
+ // Refuse prepare-deploy unless Foundry tests are green.
3
+ // Static analysis (slither) is required when available; otherwise documented skip.
4
+ // This is NOT a substitute for a paid Solidity firm audit.
5
+
6
+ import { spawnSync } from "node:child_process";
7
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { createHash } from "node:crypto";
11
+
12
+ const HERE = fileURLToPath(new URL(".", import.meta.url));
13
+ const PKG_ROOT = resolve(HERE, "../..");
14
+ const TEMPLATES_ROOT = join(PKG_ROOT, "protocols", "templates");
15
+
16
+ export const TEMPLATE_AUDIT_DISCLAIMER =
17
+ "Template + Foundry tests + optional static analysis. NOT a paid security-firm audit. Do not put mainnet TVL on forks without an independent Solidity audit.";
18
+
19
+ export function listProtocolTemplates() {
20
+ if (!existsSync(TEMPLATES_ROOT)) return [];
21
+ return readdirSync(TEMPLATES_ROOT)
22
+ .filter((name) => statSync(join(TEMPLATES_ROOT, name)).isDirectory())
23
+ .map((id) => {
24
+ const dir = join(TEMPLATES_ROOT, id);
25
+ const security = join(dir, "SECURITY.md");
26
+ return {
27
+ id,
28
+ path: dir,
29
+ securityNotice: existsSync(security) ? readFileSync(security, "utf8").slice(0, 500) : null,
30
+ disclaimer: TEMPLATE_AUDIT_DISCLAIMER,
31
+ };
32
+ });
33
+ }
34
+
35
+ function forgeBin() {
36
+ if (process.env.FORGE_BIN) return process.env.FORGE_BIN;
37
+ const home = process.env.HOME || "";
38
+ const candidates = ["forge", join(home, ".foundry/bin/forge")];
39
+ for (const c of candidates) {
40
+ const r = spawnSync(c, ["--version"], { encoding: "utf8" });
41
+ if (r.status === 0) return c;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function slitherBin() {
47
+ if (process.env.SLITHER_BIN) return process.env.SLITHER_BIN;
48
+ const r = spawnSync("slither", ["--version"], { encoding: "utf8" });
49
+ return r.status === 0 ? "slither" : null;
50
+ }
51
+
52
+ function run(cmd, args, cwd) {
53
+ const r = spawnSync(cmd, args, {
54
+ cwd,
55
+ encoding: "utf8",
56
+ env: { ...process.env, PATH: `${process.env.HOME || ""}/.foundry/bin:${process.env.PATH || ""}` },
57
+ timeout: 120_000,
58
+ });
59
+ return {
60
+ ok: r.status === 0,
61
+ status: r.status,
62
+ stdout: r.stdout || "",
63
+ stderr: r.stderr || "",
64
+ };
65
+ }
66
+
67
+ function hashTree(dir) {
68
+ const h = createHash("sha256");
69
+ function walk(p) {
70
+ const st = statSync(p);
71
+ if (st.isDirectory()) {
72
+ if (["lib", "out", "cache", ".git"].includes(p.split("/").pop())) return;
73
+ for (const name of readdirSync(p).sort()) walk(join(p, name));
74
+ } else if (st.isFile() && (p.endsWith(".sol") || p.endsWith(".toml") || p.endsWith(".md"))) {
75
+ h.update(p);
76
+ h.update(readFileSync(p));
77
+ }
78
+ }
79
+ walk(dir);
80
+ return h.digest("hex");
81
+ }
82
+
83
+ /**
84
+ * Run security gate on a template id.
85
+ * @param {string} templateId e.g. "safe-erc20"
86
+ * @param {{ allowSkipStatic?: boolean }} opts
87
+ */
88
+ export function runProtocolTemplateGate(templateId, opts = {}) {
89
+ const allowSkipStatic = opts.allowSkipStatic !== false; // default allow skip if no slither
90
+ const dir = join(TEMPLATES_ROOT, templateId);
91
+ if (!existsSync(dir)) {
92
+ throw new Error(`protocol-templates: unknown template "${templateId}"`);
93
+ }
94
+
95
+ const forge = forgeBin();
96
+ if (!forge) {
97
+ throw new Error(
98
+ "protocol-templates: forge not found. Install Foundry (https://getfoundry.sh) then: cd protocols/templates/" +
99
+ templateId +
100
+ " && forge install && forge test"
101
+ );
102
+ }
103
+
104
+ // ensure libs (not vendored in git)
105
+ if (!existsSync(join(dir, "lib", "forge-std"))) {
106
+ const a = run(forge, ["install", "foundry-rs/forge-std", "--no-git"], dir);
107
+ if (!a.ok) throw new Error(`protocol-templates: forge install forge-std failed\n${a.stderr}`);
108
+ }
109
+ if (!existsSync(join(dir, "lib", "openzeppelin-contracts"))) {
110
+ const b = run(forge, ["install", "OpenZeppelin/openzeppelin-contracts@v5.0.2", "--no-git"], dir);
111
+ if (!b.ok) throw new Error(`protocol-templates: forge install openzeppelin failed\n${b.stderr}`);
112
+ }
113
+
114
+ const tests = run(forge, ["test", "-q"], dir);
115
+ if (!tests.ok) {
116
+ throw new Error(
117
+ `protocol-templates: forge test FAILED for ${templateId}\n${tests.stdout}\n${tests.stderr}`.slice(0, 2000)
118
+ );
119
+ }
120
+
121
+ let staticAnalysis = { tool: null, ok: true, skipped: true, reason: "slither not installed" };
122
+ const slither = slitherBin();
123
+ if (slither) {
124
+ const s = run(slither, [".", "--filter-paths", "lib", "--exclude-dependencies"], dir);
125
+ staticAnalysis = {
126
+ tool: "slither",
127
+ ok: s.ok,
128
+ skipped: false,
129
+ stdout: (s.stdout || "").slice(0, 1500),
130
+ stderr: (s.stderr || "").slice(0, 500),
131
+ };
132
+ if (!s.ok && !opts.allowStaticFail) {
133
+ throw new Error(`protocol-templates: slither FAILED for ${templateId}\n${s.stdout}\n${s.stderr}`.slice(0, 2000));
134
+ }
135
+ } else if (!allowSkipStatic) {
136
+ throw new Error("protocol-templates: slither required (allowSkipStatic=false) but not installed");
137
+ }
138
+
139
+ const sourceHash = hashTree(dir);
140
+ return {
141
+ ok: true,
142
+ templateId,
143
+ path: dir,
144
+ forgeTests: { ok: true, tool: "forge", summary: "forge test passed" },
145
+ staticAnalysis,
146
+ sourceHash,
147
+ disclaimer: TEMPLATE_AUDIT_DISCLAIMER,
148
+ firmAudit: false,
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Build artifact after gate. Returns bytecode for unsigned deploy prepare.
154
+ */
155
+ export function buildProtocolTemplate(templateId, opts = {}) {
156
+ const gate = runProtocolTemplateGate(templateId, opts);
157
+ const forge = forgeBin();
158
+ const dir = gate.path;
159
+ const build = run(forge, ["build", "-q"], dir);
160
+ if (!build.ok) {
161
+ throw new Error(`protocol-templates: forge build failed\n${build.stderr}`.slice(0, 1500));
162
+ }
163
+ // default contract name heuristic
164
+ const contractName = opts.contractName || (templateId === "safe-erc20" ? "SafeERC20" : null);
165
+ if (!contractName) throw new Error("protocol-templates: contractName required");
166
+ const artifactPath = join(dir, "out", `${contractName}.sol`, `${contractName}.json`);
167
+ if (!existsSync(artifactPath)) {
168
+ throw new Error(`protocol-templates: artifact missing at ${artifactPath}`);
169
+ }
170
+ const artifact = JSON.parse(readFileSync(artifactPath, "utf8"));
171
+ return {
172
+ gate,
173
+ contractName,
174
+ abi: artifact.abi,
175
+ bytecode: artifact.bytecode?.object || artifact.bytecode,
176
+ deployedBytecode: artifact.deployedBytecode?.object || artifact.deployedBytecode,
177
+ artifactPath,
178
+ };
179
+ }
@@ -0,0 +1,81 @@
1
+ // Prepare unsigned deploy for a gated protocol template.
2
+ // Never signs. Never broadcasts. Requires green forge tests first.
3
+
4
+ import { ContractFactory, isAddress } from "ethers";
5
+ import { stampPrepared } from "../prepare-envelope.mjs";
6
+ import { buildProtocolTemplate, listProtocolTemplates, TEMPLATE_AUDIT_DISCLAIMER } from "./gate.mjs";
7
+
8
+ export { listProtocolTemplates, TEMPLATE_AUDIT_DISCLAIMER };
9
+
10
+ /**
11
+ * @param {object} args
12
+ * @param {string} args.templateId - e.g. safe-erc20
13
+ * @param {number} args.chainId
14
+ * @param {string[]} [args.constructorArgs] - ABI-encoded via ethers from values
15
+ * @param {any[]} [args.args] - constructor JS values (preferred)
16
+ * @param {string} [args.contractName]
17
+ */
18
+ export async function prepareTemplateDeploy(args = {}, opts = {}) {
19
+ const templateId = String(args.templateId || "").trim();
20
+ if (!templateId) throw new Error("prepareTemplateDeploy: templateId required");
21
+ const chainId = Number(args.chainId);
22
+ if (!Number.isInteger(chainId) || chainId <= 0) {
23
+ throw new Error("prepareTemplateDeploy: chainId required");
24
+ }
25
+
26
+ const built = buildProtocolTemplate(templateId, {
27
+ contractName: args.contractName,
28
+ allowSkipStatic: opts.allowSkipStatic !== false,
29
+ allowStaticFail: opts.allowStaticFail === true,
30
+ });
31
+
32
+ if (!built.bytecode || built.bytecode === "0x") {
33
+ throw new Error("prepareTemplateDeploy: empty bytecode");
34
+ }
35
+
36
+ const bc = built.bytecode.startsWith("0x") ? built.bytecode : `0x${built.bytecode}`;
37
+ const ctorArgs = args.args ?? args.constructorArgs ?? [];
38
+ const factory = new ContractFactory(built.abi, bc);
39
+ const deployTx = await factory.getDeployTransaction(...(Array.isArray(ctorArgs) ? ctorArgs : []));
40
+ const data = deployTx.data;
41
+
42
+ // validate SafeERC20 constructor shape if applicable
43
+ if (templateId === "safe-erc20" && Array.isArray(ctorArgs)) {
44
+ const [name, symbol, supply, holder, owner] = ctorArgs;
45
+ if (!name || !symbol) throw new Error("safe-erc20: name/symbol required");
46
+ if (supply == null || BigInt(supply) <= 0n) throw new Error("safe-erc20: supply must be > 0");
47
+ if (!isAddress(String(holder))) throw new Error("safe-erc20: initialHolder must be address");
48
+ if (!isAddress(String(owner))) throw new Error("safe-erc20: initialOwner must be address");
49
+ }
50
+
51
+ return stampPrepared(
52
+ {
53
+ provider: "protocol-templates",
54
+ kind: "template-deploy",
55
+ templateId,
56
+ contractName: built.contractName,
57
+ chainId,
58
+ transaction: {
59
+ chainId,
60
+ to: null, // creation
61
+ data,
62
+ value: "0x0",
63
+ },
64
+ abi: built.abi,
65
+ bytecodeHash: built.gate.sourceHash,
66
+ gate: {
67
+ forgeTests: built.gate.forgeTests,
68
+ staticAnalysis: {
69
+ tool: built.gate.staticAnalysis.tool,
70
+ ok: built.gate.staticAnalysis.ok,
71
+ skipped: built.gate.staticAnalysis.skipped,
72
+ },
73
+ firmAudit: false,
74
+ disclaimer: TEMPLATE_AUDIT_DISCLAIMER,
75
+ },
76
+ requiresUserSignature: true,
77
+ note: "Unsigned deploy. User wallet signs. Template tests passed; not a firm audit.",
78
+ },
79
+ { provider: "protocol-templates", kind: "template-deploy" }
80
+ );
81
+ }