openclaw-looloo 0.1.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.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # openclaw-looloo
2
+
3
+ `openclaw-looloo` is the phase 1 OpenClaw plugin for LooLoo.
4
+
5
+ It lets users:
6
+
7
+ - discover new internal-market tokens
8
+ - inspect token summaries
9
+ - check current positions
10
+ - fetch buy and sell quotes
11
+ - create website confirmation links for trades
12
+
13
+ ## Phase 1 scope
14
+
15
+ This package is intentionally limited to the phase 1 workflow:
16
+
17
+ - `OpenClaw` handles discovery, quote lookup, and trade intent creation
18
+ - `looloo.lol` handles final wallet confirmation
19
+
20
+ This plugin does **not**:
21
+
22
+ - sign transactions directly
23
+ - submit onchain trades directly
24
+ - run unattended auto-trading
25
+ - launch new tokens
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ openclaw plugins install openclaw-looloo
31
+ ```
32
+
33
+ ## Configure
34
+
35
+ Create an automation key from `looloo.lol`:
36
+
37
+ 1. Open `Settings -> OpenClaw`
38
+ 2. Create an automation key
39
+ 3. Copy the key once when shown
40
+
41
+ Set the plugin config or environment variables:
42
+
43
+ ```bash
44
+ export LOOLOO_API_BASE="https://looloo.lol"
45
+ export LOOLOO_AUTOMATION_KEY="llak_..."
46
+ ```
47
+
48
+ ## Tools
49
+
50
+ - `discover_new_tokens`
51
+ - `get_token_summary`
52
+ - `get_positions`
53
+ - `quote_trade`
54
+ - `create_trade_intent`
55
+ - `get_automation_profile`
56
+
57
+ ## Example flow
58
+
59
+ 1. Discover or inspect a token from chat.
60
+ 2. Ask for a quote.
61
+ 3. Create a trade intent.
62
+ 4. Open the returned `confirmUrl`.
63
+ 5. Confirm the transaction in `looloo.lol` with the user's wallet.
64
+
65
+ ## Example prompts
66
+
67
+ - `Scan the newest LooLoo internal-market tokens`
68
+ - `Show me a summary for 0xTokenAddress`
69
+ - `Quote a buy of 0.05 ETH for 0xTokenAddress`
70
+ - `Create a trade intent to sell 100000 tokens`
71
+ - `Show my current LooLoo positions`
72
+
73
+ ## Notes
74
+
75
+ - Phase 1 only supports the `internal` market.
76
+ - Notifications should use the user's existing OpenClaw chat channel.
77
+ - The plugin is an execution helper, not a profit guarantee or autonomous trading bot.
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "name": "openclaw-looloo",
4
+ "displayName": "LooLoo",
5
+ "version": "0.1.1",
6
+ "description": "Phase 1 LooLoo plugin for token discovery, quotes, positions, and website trade confirmation links.",
7
+ "entry": "./src/index.mjs",
8
+ "skills": [
9
+ "./skills/looloo-discovery/SKILL.md",
10
+ "./skills/looloo-trading/SKILL.md"
11
+ ]
12
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "openclaw-looloo",
3
+ "version": "0.1.1",
4
+ "description": "Phase 1 OpenClaw plugin for LooLoo discovery, quotes, positions, and trade intents.",
5
+ "type": "module",
6
+ "main": "./src/index.mjs",
7
+ "openclaw.extensions": [
8
+ "./openclaw.plugin.json"
9
+ ],
10
+ "files": [
11
+ "openclaw.plugin.json",
12
+ "README.md",
13
+ "src",
14
+ "skills"
15
+ ],
16
+ "scripts": {
17
+ "lint": "node --check ./src/index.mjs"
18
+ },
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "keywords": [
23
+ "openclaw",
24
+ "looloo",
25
+ "plugin",
26
+ "skills",
27
+ "trading",
28
+ "automation",
29
+ "phase-1"
30
+ ],
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "openclaw-looloo": "file:openclaw-looloo-0.1.1.tgz"
34
+ }
35
+ }
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: looloo-discovery
3
+ description: Use this skill when the user wants to discover new LooLoo tokens, inspect token activity, or review current positions from OpenClaw.
4
+ ---
5
+
6
+ # LooLoo Discovery
7
+
8
+ Use the bundled `openclaw-looloo` plugin tools instead of calling the raw API directly.
9
+
10
+ ## Use this skill for
11
+
12
+ - scanning new internal-market tokens
13
+ - reviewing one token's recent trades and top holders
14
+ - checking the authenticated wallet's current positions
15
+
16
+ ## Workflow
17
+
18
+ 1. Call `discover_new_tokens` for a market-wide scan.
19
+ 2. If the user narrows to one token, call `get_token_summary`.
20
+ 3. If the user asks about their wallet, call `get_positions`.
21
+ 4. Summarize the strongest signals briefly and keep raw JSON available if needed.
22
+
23
+ ## Notes
24
+
25
+ - Phase 1 only supports the `internal` market.
26
+ - OpenClaw should return notifications through the user's existing chat channel.
27
+ - Do not imply guaranteed profits; frame results as discovery and execution support.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: looloo-trading
3
+ description: Use this skill when the user wants a LooLoo trade quote or a website confirmation link for a buy or sell.
4
+ ---
5
+
6
+ # LooLoo Trading
7
+
8
+ Phase 1 trading is quote-first and wallet-confirmed.
9
+
10
+ ## Use this skill for
11
+
12
+ - quoting a buy or sell on the LooLoo internal market
13
+ - creating a confirmation link that sends the user back to `looloo.lol`
14
+
15
+ ## Workflow
16
+
17
+ 1. Call `quote_trade` with `tokenAddress`, `side`, and either `amountETH` or `amountToken`.
18
+ 2. Show the quote summary and confirm the side and size with the user.
19
+ 3. Call `create_trade_intent` only after the user explicitly wants to continue.
20
+ 4. Return the `confirmUrl` and explain that the final wallet signature happens on the LooLoo website.
21
+
22
+ ## Notes
23
+
24
+ - Phase 1 does not support unattended execution.
25
+ - The plugin does not hold keys or sign transactions.
26
+ - If the API rejects an intent, surface the policy error directly because it usually comes from the user's risk settings.
package/src/index.mjs ADDED
@@ -0,0 +1,270 @@
1
+ const DEFAULT_API_BASE = "https://looloo.lol";
2
+
3
+ function getConfig(overrides = {}) {
4
+ const apiBase = (overrides.apiBase || process.env.LOOLOO_API_BASE || DEFAULT_API_BASE).replace(/\/+$/, "");
5
+ const apiKey = overrides.apiKey || process.env.LOOLOO_AUTOMATION_KEY || "";
6
+ if (!apiKey) {
7
+ throw new Error("LOOLOO_AUTOMATION_KEY is required");
8
+ }
9
+ return { apiBase, apiKey };
10
+ }
11
+
12
+ async function request(path, options = {}, overrides = {}) {
13
+ const { apiBase, apiKey } = getConfig(overrides);
14
+ const url = `${apiBase}${path}`;
15
+ const headers = {
16
+ Accept: "application/json",
17
+ Authorization: `Bearer ${apiKey}`,
18
+ ...options.headers,
19
+ };
20
+ if (options.body && !headers["Content-Type"]) {
21
+ headers["Content-Type"] = "application/json";
22
+ }
23
+
24
+ const response = await fetch(url, {
25
+ ...options,
26
+ headers,
27
+ });
28
+
29
+ const raw = await response.text();
30
+ let data = null;
31
+ if (raw) {
32
+ try {
33
+ data = JSON.parse(raw);
34
+ } catch (error) {
35
+ throw new Error(`Unexpected response from ${path}: ${raw}`);
36
+ }
37
+ }
38
+
39
+ if (!response.ok) {
40
+ const message =
41
+ (data && (data.error || data.message || data.details)) ||
42
+ response.statusText ||
43
+ `HTTP ${response.status}`;
44
+ throw new Error(message);
45
+ }
46
+
47
+ return data;
48
+ }
49
+
50
+ function toQuery(params = {}) {
51
+ const entries = Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== "");
52
+ if (entries.length === 0) {
53
+ return "";
54
+ }
55
+ const query = new URLSearchParams();
56
+ for (const [key, value] of entries) {
57
+ query.set(key, String(value));
58
+ }
59
+ return `?${query.toString()}`;
60
+ }
61
+
62
+ async function discoverNewTokens(input = {}, overrides = {}) {
63
+ const params = {
64
+ limit: input.limit,
65
+ market: input.market || "internal",
66
+ };
67
+ return request(`/v1/automation/discover${toQuery(params)}`, { method: "GET" }, overrides);
68
+ }
69
+
70
+ async function getTokenSummary(input = {}, overrides = {}) {
71
+ if (!input.tokenAddress) {
72
+ throw new Error("tokenAddress is required");
73
+ }
74
+ return request(`/v1/automation/tokens/${encodeURIComponent(input.tokenAddress)}`, { method: "GET" }, overrides);
75
+ }
76
+
77
+ async function getPositions(input = {}, overrides = {}) {
78
+ const params = {
79
+ limit: input.limit,
80
+ market: input.market || "internal",
81
+ };
82
+ return request(`/v1/automation/positions${toQuery(params)}`, { method: "GET" }, overrides);
83
+ }
84
+
85
+ async function quoteTrade(input = {}, overrides = {}) {
86
+ if (!input.tokenAddress) {
87
+ throw new Error("tokenAddress is required");
88
+ }
89
+ if (!input.side) {
90
+ throw new Error("side is required");
91
+ }
92
+ return request(
93
+ "/v1/automation/trades/quote",
94
+ {
95
+ method: "POST",
96
+ body: JSON.stringify({
97
+ tokenAddress: input.tokenAddress,
98
+ side: input.side,
99
+ amountETH: input.amountETH || "",
100
+ amountToken: input.amountToken || "",
101
+ }),
102
+ },
103
+ overrides,
104
+ );
105
+ }
106
+
107
+ async function createTradeIntent(input = {}, overrides = {}) {
108
+ if (!input.tokenAddress) {
109
+ throw new Error("tokenAddress is required");
110
+ }
111
+ if (!input.side) {
112
+ throw new Error("side is required");
113
+ }
114
+ return request(
115
+ "/v1/automation/trade-intents",
116
+ {
117
+ method: "POST",
118
+ body: JSON.stringify({
119
+ tokenAddress: input.tokenAddress,
120
+ side: input.side,
121
+ amountETH: input.amountETH || "",
122
+ amountToken: input.amountToken || "",
123
+ }),
124
+ },
125
+ overrides,
126
+ );
127
+ }
128
+
129
+ async function getAutomationProfile(_input = {}, overrides = {}) {
130
+ return request("/v1/automation/me", { method: "GET" }, overrides);
131
+ }
132
+
133
+ function jsonResult(data) {
134
+ return {
135
+ content: [
136
+ {
137
+ type: "json",
138
+ json: data,
139
+ },
140
+ ],
141
+ };
142
+ }
143
+
144
+ export const tools = [
145
+ {
146
+ name: "discover_new_tokens",
147
+ description: "List the newest LooLoo tokens for the internal market.",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ limit: { type: "integer", minimum: 1, maximum: 100 },
152
+ market: { type: "string", enum: ["internal"] },
153
+ },
154
+ },
155
+ async run(input, context) {
156
+ return jsonResult(await discoverNewTokens(input, context?.config));
157
+ },
158
+ },
159
+ {
160
+ name: "get_token_summary",
161
+ description: "Fetch one token summary with latest trades and top holders.",
162
+ inputSchema: {
163
+ type: "object",
164
+ required: ["tokenAddress"],
165
+ properties: {
166
+ tokenAddress: { type: "string" },
167
+ },
168
+ },
169
+ async run(input, context) {
170
+ return jsonResult(await getTokenSummary(input, context?.config));
171
+ },
172
+ },
173
+ {
174
+ name: "get_positions",
175
+ description: "Fetch the authenticated wallet's current LooLoo positions.",
176
+ inputSchema: {
177
+ type: "object",
178
+ properties: {
179
+ limit: { type: "integer", minimum: 1, maximum: 100 },
180
+ market: { type: "string", enum: ["internal"] },
181
+ },
182
+ },
183
+ async run(input, context) {
184
+ return jsonResult(await getPositions(input, context?.config));
185
+ },
186
+ },
187
+ {
188
+ name: "quote_trade",
189
+ description: "Get a quote for a buy or sell on the LooLoo internal market.",
190
+ inputSchema: {
191
+ type: "object",
192
+ required: ["tokenAddress", "side"],
193
+ properties: {
194
+ tokenAddress: { type: "string" },
195
+ side: { type: "string", enum: ["buy", "sell"] },
196
+ amountETH: { type: "string" },
197
+ amountToken: { type: "string" },
198
+ },
199
+ },
200
+ async run(input, context) {
201
+ return jsonResult(await quoteTrade(input, context?.config));
202
+ },
203
+ },
204
+ {
205
+ name: "create_trade_intent",
206
+ description: "Create a website confirmation link for a quoted trade.",
207
+ inputSchema: {
208
+ type: "object",
209
+ required: ["tokenAddress", "side"],
210
+ properties: {
211
+ tokenAddress: { type: "string" },
212
+ side: { type: "string", enum: ["buy", "sell"] },
213
+ amountETH: { type: "string" },
214
+ amountToken: { type: "string" },
215
+ },
216
+ },
217
+ async run(input, context) {
218
+ return jsonResult(await createTradeIntent(input, context?.config));
219
+ },
220
+ },
221
+ {
222
+ name: "get_automation_profile",
223
+ description: "Inspect the active LooLoo automation identity and scopes.",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {},
227
+ },
228
+ async run(input, context) {
229
+ return jsonResult(await getAutomationProfile(input, context?.config));
230
+ },
231
+ },
232
+ ];
233
+
234
+ export function createPlugin(overrides = {}) {
235
+ return {
236
+ name: "openclaw-looloo",
237
+ configSchema: {
238
+ type: "object",
239
+ properties: {
240
+ apiBase: {
241
+ type: "string",
242
+ description: "Base URL for the LooLoo API.",
243
+ default: DEFAULT_API_BASE,
244
+ },
245
+ apiKey: {
246
+ type: "string",
247
+ description: "Automation key created from looloo.lol settings.",
248
+ },
249
+ },
250
+ required: ["apiKey"],
251
+ },
252
+ tools: tools.map((tool) => ({
253
+ ...tool,
254
+ run: (input, context = {}) => tool.run(input, { ...context, config: { ...overrides, ...context.config } }),
255
+ })),
256
+ };
257
+ }
258
+
259
+ export {
260
+ DEFAULT_API_BASE,
261
+ createTradeIntent,
262
+ discoverNewTokens,
263
+ getAutomationProfile,
264
+ getPositions,
265
+ getTokenSummary,
266
+ quoteTrade,
267
+ request,
268
+ };
269
+
270
+ export default createPlugin();