@mint.club/v2-eliza-plugin 2.0.0 → 2.0.2
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/LICENSE +21 -0
- package/README.md +22 -10
- package/dist/commands.js +188 -45
- package/dist/plugin.js +9 -8
- package/package.json +9 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mint Club contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -22,6 +22,8 @@ The plugin exposes nine protocol-specific actions:
|
|
|
22
22
|
| `SEND_TOKEN` | Send native currency or an ERC-20 token |
|
|
23
23
|
| `CREATE_TOKEN` | Create an ERC-20 bonding curve token |
|
|
24
24
|
|
|
25
|
+
`CREATE_TOKEN` performs onchain creation through the CLI. Configure logos and website links afterward using the creator wallet on Mint Club; see the [CLI metadata guidance](../cli/README.md#create-a-token). The plugin does not call the metadata API.
|
|
26
|
+
|
|
25
27
|
There is no general-purpose DEX swap action. Zap routing checks only direct and one-intermediary homogeneous V2/V3/V4 paths by RPC.
|
|
26
28
|
|
|
27
29
|
## Setup
|
|
@@ -30,7 +32,7 @@ There is no general-purpose DEX swap action. Zap routing checks only direct and
|
|
|
30
32
|
npm install @mint.club/v2-eliza-plugin
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
Requires Node.js
|
|
35
|
+
Requires Node.js 22.13 or later and an ElizaOS project compatible with `@elizaos/core ^1.7.2`. The plugin installs a compatible `@mint.club/v2-cli` 2.x runtime dependency automatically.
|
|
34
36
|
|
|
35
37
|
Add the plugin to an ElizaOS character configuration:
|
|
36
38
|
|
|
@@ -46,29 +48,39 @@ Read-only actions work without a wallet. Write actions and wallet balances requi
|
|
|
46
48
|
|
|
47
49
|
> Never store a key in a character file, commit it, or paste it into an agent conversation. Use a dedicated wallet with limited funds.
|
|
48
50
|
|
|
51
|
+
A write action runs only when the **original user message** is one affirmative `Confirm:` statement. The parser rejects question-form confirmations, non-ASCII whitespace, control/format/bidirectional characters, negation, cancellation, multiple write clauses, and any unmatched text. The model and plugin must never add this prefix on the user’s behalf; a bare follow-up such as “yes” is insufficient.
|
|
52
|
+
|
|
53
|
+
Every confirmation must name exactly one chain and bind every effective financial parameter:
|
|
54
|
+
|
|
55
|
+
- direct buys: `with maximum cost AMOUNT reserve units`
|
|
56
|
+
- direct sells: `with minimum refund AMOUNT reserve units`
|
|
57
|
+
- routed buys/sells: an explicit slippage percentage
|
|
58
|
+
- sends: the native or ERC-20 asset, amount, recipient, and chain
|
|
59
|
+
- token creation: a printable ASCII name without transaction instructions, plus mint and burn royalties in basis points
|
|
60
|
+
|
|
49
61
|
## Example prompts
|
|
50
62
|
|
|
51
63
|
- “Get info about SIGNET.”
|
|
52
64
|
- “What is the price of TOKEN on Robinhood?”
|
|
53
|
-
- “Buy 25 SIGNET.”
|
|
54
|
-
- “Sell 5 SIGNET.”
|
|
55
|
-
- “Buy TOKEN with 10 USDT on Arbitrum with 0.5% slippage.”
|
|
56
|
-
- “Sell 5 TOKEN for USDC on Unichain.”
|
|
65
|
+
- “Confirm: Buy 25 SIGNET on Base with maximum cost 30 reserve units.”
|
|
66
|
+
- “Confirm: Sell 5 SIGNET on Base with minimum refund 4 reserve units.”
|
|
67
|
+
- “Confirm: Buy TOKEN with 10 USDT on Arbitrum with 0.5% slippage.”
|
|
68
|
+
- “Confirm: Sell 5 TOKEN for USDC on Unichain with 1% slippage.”
|
|
57
69
|
- “Show my wallet balance on Polygon.”
|
|
58
|
-
- “Send 10 USDG to `0x1111111111111111111111111111111111111111` on Robinhood.”
|
|
59
|
-
- “Create token \"My Token\" (MYT) backed by USDG with max supply 1000000 using a linear curve from 0.01 to 1 on Robinhood.”
|
|
70
|
+
- “Confirm: Send 10 USDG to `0x1111111111111111111111111111111111111111` on Robinhood.”
|
|
71
|
+
- “Confirm: Create token \"My Token\" (MYT) backed by USDG with max supply 1000000 using a linear curve from 0.01 to 1 on Robinhood with 100 bps mint royalty and 100 bps burn royalty.”
|
|
60
72
|
|
|
61
|
-
The Zap parser deliberately rejects the ambiguous target-amount form “Buy 10 TOKEN with ETH.” Use “Buy TOKEN with 0.1 ETH” so the exact routed input amount is explicit. Zap slippage
|
|
73
|
+
The Zap parser deliberately rejects the ambiguous target-amount form “Buy 10 TOKEN with ETH.” Use “Buy TOKEN with 0.1 ETH” so the exact routed input amount is explicit. The plugin does not accept an implicit Zap slippage default in a confirmed write.
|
|
62
74
|
|
|
63
75
|
The target Mint Club token may be ERC-20 or ERC-1155; ERC-1155 amounts must be whole numbers. Routed input/output and `SEND_TOKEN` assets must be native currency or ERC-20 tokens.
|
|
64
76
|
|
|
65
|
-
The text parser fails closed on
|
|
77
|
+
The text parser fails closed on constraints it cannot map without ambiguity. Direct trade limits use numeric reserve-token units, and token-creation royalties use integer basis points. Use the CLI or MCP server when you need explicit routed minimum-output values instead of percentage slippage.
|
|
66
78
|
|
|
67
79
|
Supported canonical chain keys:
|
|
68
80
|
|
|
69
81
|
`ethereum` · `optimism` · `arbitrum` · `avalanche` · `base` · `polygon` · `bsc` · `zora` · `unichain` · `robinhood` · `sepolia` · `base-sepolia`
|
|
70
82
|
|
|
71
|
-
Natural-language aliases such as “Ethereum mainnet,” “Ethereum Sepolia,” “Base Sepolia,” “Arbitrum One,” “BNB Chain,” and “Robinhood Chain” are loaded from the central registry. Base is the default. Contradictory or
|
|
83
|
+
Natural-language aliases such as “Ethereum mainnet,” “Ethereum Sepolia,” “Base Sepolia,” “Arbitrum One,” “BNB Chain,” and “Robinhood Chain” are loaded from the central registry. Base is the default only for reads; every write confirmation must name one chain explicitly. Contradictory or repeated chain instructions are rejected rather than guessed.
|
|
72
84
|
|
|
73
85
|
ZapV2 is deployed on every supported chain listed above. Blast is unsupported by this integration.
|
|
74
86
|
|
package/dist/commands.js
CHANGED
|
@@ -2,6 +2,58 @@ import { createRequire } from 'module';
|
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
3
|
const chainRegistry = require('@mint.club/v2-cli/chain-registry.json');
|
|
4
4
|
export const SUPPORTED_CHAINS = Object.freeze(chainRegistry.chains.map(({ key }) => key));
|
|
5
|
+
function matchSpan(match) {
|
|
6
|
+
const start = match.index;
|
|
7
|
+
if (start === undefined)
|
|
8
|
+
throw new Error('Parser match is missing its source position');
|
|
9
|
+
return [start, start + match[0].length];
|
|
10
|
+
}
|
|
11
|
+
function assertFullyParsed(text, spans) {
|
|
12
|
+
const ordered = [...spans].sort(([left], [right]) => left - right);
|
|
13
|
+
for (let index = 1; index < ordered.length; index += 1) {
|
|
14
|
+
if (ordered[index][0] < ordered[index - 1][1]) {
|
|
15
|
+
throw new Error('Confirmed write contains overlapping transaction clauses');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const remaining = text.split('');
|
|
19
|
+
for (const [start, end] of spans) {
|
|
20
|
+
for (let index = start; index < end; index += 1)
|
|
21
|
+
remaining[index] = ' ';
|
|
22
|
+
}
|
|
23
|
+
const unmatched = remaining
|
|
24
|
+
.join('')
|
|
25
|
+
.replace(/[.,!?;]+/g, ' ')
|
|
26
|
+
.trim();
|
|
27
|
+
if (unmatched) {
|
|
28
|
+
throw new Error(`Confirmed write contains unsupported or unmatched text: "${unmatched}"`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const WRITE_ACTIONS = new Set([
|
|
32
|
+
'BUY_TOKEN',
|
|
33
|
+
'SELL_TOKEN',
|
|
34
|
+
'ZAP_BUY',
|
|
35
|
+
'ZAP_SELL',
|
|
36
|
+
'SEND_TOKEN',
|
|
37
|
+
'CREATE_TOKEN',
|
|
38
|
+
]);
|
|
39
|
+
const UNSAFE_CONFIRMATION_CHARS = /[^\S ]|[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]/u;
|
|
40
|
+
const TRANSACTION_NAME_WORDS = /(?:^|[^a-z])(?:buy|mint|sell|burn|send|transfer|create|launch|approve|revoke|execute|confirm)(?=$|[^a-z])/i;
|
|
41
|
+
function confirmationEnvelopeSpan(text) {
|
|
42
|
+
const confirmation = text.match(/^\s*confirm\s*:\s+/i);
|
|
43
|
+
if (!confirmation) {
|
|
44
|
+
throw new Error('Write requests must start with "Confirm:" and repeat the full transaction details');
|
|
45
|
+
}
|
|
46
|
+
if (UNSAFE_CONFIRMATION_CHARS.test(text)) {
|
|
47
|
+
throw new Error('Confirmed writes must use printable text with ASCII spaces; control, formatting, and bidirectional characters are not allowed');
|
|
48
|
+
}
|
|
49
|
+
if (text.includes('?')) {
|
|
50
|
+
throw new Error('Confirmed writes must be affirmative statements, not questions');
|
|
51
|
+
}
|
|
52
|
+
if (/\b(?:do\s+not|don['’]?t|not|never|cancel|abort|stop|without|except|no\s+longer)\b/i.test(text)) {
|
|
53
|
+
throw new Error('Confirmed write must be a single affirmative transaction without negation or cancellation');
|
|
54
|
+
}
|
|
55
|
+
return matchSpan(confirmation);
|
|
56
|
+
}
|
|
5
57
|
function escapeRegExp(value) {
|
|
6
58
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
59
|
}
|
|
@@ -48,17 +100,25 @@ function resolveChainAlias(alias) {
|
|
|
48
100
|
throw new Error(`Unknown chain alias: ${alias}`);
|
|
49
101
|
return chain;
|
|
50
102
|
}
|
|
51
|
-
function
|
|
103
|
+
function chainSelection(text, strictWrite = false) {
|
|
52
104
|
const negated = new Set();
|
|
53
105
|
const negation = new RegExp(`\\b(?:not|without|except)\\s+(?:on\\s+)?(?:the\\s+)?(${CHAIN_ALIAS_PATTERN})(?:\\s+chain)?\\b`, 'gi');
|
|
54
|
-
const positiveText = text.replace(negation, (
|
|
106
|
+
const positiveText = text.replace(negation, (match, alias) => {
|
|
55
107
|
negated.add(resolveChainAlias(alias));
|
|
56
|
-
return ' ';
|
|
108
|
+
return ' '.repeat(match.length);
|
|
57
109
|
});
|
|
110
|
+
if (positiveText.length !== text.length) {
|
|
111
|
+
throw new Error('Chain parser span alignment failed');
|
|
112
|
+
}
|
|
58
113
|
const mentioned = new Set();
|
|
59
|
-
const
|
|
114
|
+
const spans = [];
|
|
115
|
+
const contextSuffix = strictWrite
|
|
116
|
+
? '(?:chain|mainnet)'
|
|
117
|
+
: '(?:chain|mainnet|wallet|balance|holdings)';
|
|
118
|
+
const positive = new RegExp(`(?:\\bon\\s+(?:the\\s+)?(${CHAIN_ALIAS_PATTERN})(?:\\s+chain)?\\b|\\b(${CHAIN_ALIAS_PATTERN})\\s+${contextSuffix}\\b)`, 'gi');
|
|
60
119
|
for (const match of positiveText.matchAll(positive)) {
|
|
61
120
|
mentioned.add(resolveChainAlias(match[1] ?? match[2]));
|
|
121
|
+
spans.push(matchSpan(match));
|
|
62
122
|
}
|
|
63
123
|
const hasConflict = [...mentioned].some((chain) => negated.has(chain));
|
|
64
124
|
if (mentioned.size > 1 ||
|
|
@@ -67,7 +127,7 @@ function chainArgs(text) {
|
|
|
67
127
|
throw new Error(`Specify exactly one chain: ${SUPPORTED_CHAINS.join(', ')}`);
|
|
68
128
|
}
|
|
69
129
|
const selected = mentioned.values().next().value ?? 'base';
|
|
70
|
-
return ['--chain', selected];
|
|
130
|
+
return { args: ['--chain', selected], spans, mentionCount: spans.length };
|
|
71
131
|
}
|
|
72
132
|
const TOKEN_PATTERN = '(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)';
|
|
73
133
|
const ASSET_PATTERN = '(native(?:\\s+currency)?|0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)';
|
|
@@ -80,16 +140,33 @@ function rejectUnsupportedIntent(text, pattern, description) {
|
|
|
80
140
|
throw new Error(`${description} is not supported by this text parser`);
|
|
81
141
|
}
|
|
82
142
|
}
|
|
143
|
+
function tradeLimitPattern(action) {
|
|
144
|
+
const limit = action === 'buy'
|
|
145
|
+
? 'max(?:imum)?\\s+cost'
|
|
146
|
+
: 'min(?:imum)?\\s+refund';
|
|
147
|
+
return new RegExp(`\\bwith\\s+(?:a\\s+)?${limit}\\s+${AMOUNT_PATTERN}\\s+reserve\\s+(?:tokens?|units?)\\b`, 'i');
|
|
148
|
+
}
|
|
149
|
+
function tradeLimitParts(text, action) {
|
|
150
|
+
const match = text.match(tradeLimitPattern(action));
|
|
151
|
+
if (!match) {
|
|
152
|
+
throw new Error(action === 'buy'
|
|
153
|
+
? 'Confirmed buy must include "with maximum cost AMOUNT reserve units"'
|
|
154
|
+
: 'Confirmed sell must include "with minimum refund AMOUNT reserve units"');
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
args: [action === 'buy' ? '--max-cost' : '--min-refund', match[1]],
|
|
158
|
+
span: matchSpan(match),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
83
161
|
function tradeParts(text, action) {
|
|
84
|
-
rejectUnsupportedIntent(text, action === 'buy'
|
|
85
|
-
? /\bmax(?:imum)?\s+cost\b/i
|
|
86
|
-
: /\bmin(?:imum)?\s+refund\b/i, action === 'buy' ? 'Maximum cost' : 'Minimum refund');
|
|
87
162
|
const verbs = action === 'buy' ? '(?:buy|mint)' : '(?:sell|burn)';
|
|
88
163
|
const match = text.match(new RegExp(`\\b${verbs}\\s+${AMOUNT_PATTERN}\\s+(?:of\\s+)?${TOKEN_PATTERN}\\b`, 'i'));
|
|
89
164
|
if (!match) {
|
|
90
165
|
throw new Error('Specify an amount and token, for example: "buy 10 SIGNET"');
|
|
91
166
|
}
|
|
92
|
-
const trailingText = text
|
|
167
|
+
const trailingText = text
|
|
168
|
+
.slice((match.index ?? 0) + match[0].length)
|
|
169
|
+
.replace(tradeLimitPattern(action), ' ');
|
|
93
170
|
const routedIntent = action === 'buy'
|
|
94
171
|
? /\b(?:with|using|from)\b/i
|
|
95
172
|
: /\b(?:for|to|into)\b/i;
|
|
@@ -98,7 +175,7 @@ function tradeParts(text, action) {
|
|
|
98
175
|
? 'Use the exact-input form: Buy TOKEN with AMOUNT INPUT_TOKEN'
|
|
99
176
|
: 'Use the routed-output form: Sell AMOUNT TOKEN for OUTPUT_TOKEN');
|
|
100
177
|
}
|
|
101
|
-
return { amount: match[1], token: match[2] };
|
|
178
|
+
return { amount: match[1], token: match[2], span: matchSpan(match) };
|
|
102
179
|
}
|
|
103
180
|
function zapBuyParts(text) {
|
|
104
181
|
rejectUnsupportedIntent(text, /\bmin(?:imum)?\s+tokens?\b/i, 'Minimum token output');
|
|
@@ -110,6 +187,7 @@ function zapBuyParts(text) {
|
|
|
110
187
|
token: match[1],
|
|
111
188
|
inputAmount: match[2],
|
|
112
189
|
inputToken: normalizeAsset(match[3]),
|
|
190
|
+
span: matchSpan(match),
|
|
113
191
|
};
|
|
114
192
|
}
|
|
115
193
|
function zapSellParts(text) {
|
|
@@ -122,11 +200,18 @@ function zapSellParts(text) {
|
|
|
122
200
|
amount: match[1],
|
|
123
201
|
token: match[2],
|
|
124
202
|
outputToken: normalizeAsset(match[3]),
|
|
203
|
+
span: matchSpan(match),
|
|
125
204
|
};
|
|
126
205
|
}
|
|
127
|
-
function
|
|
206
|
+
function slippageParts(text) {
|
|
128
207
|
const match = text.match(/\b(?:with|at)?\s*([0-9]+(?:\.[0-9]+)?)\s*%\s+slippage\b/i);
|
|
129
|
-
|
|
208
|
+
if (!match) {
|
|
209
|
+
throw new Error('Confirmed routed write must include an explicit slippage percentage');
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
args: ['--slippage', match[1]],
|
|
213
|
+
spans: [matchSpan(match)],
|
|
214
|
+
};
|
|
130
215
|
}
|
|
131
216
|
function tokenFromText(text, action) {
|
|
132
217
|
const patterns = action === 'info'
|
|
@@ -148,9 +233,9 @@ function tokenFromText(text, action) {
|
|
|
148
233
|
throw new Error(`Specify a token for ${action}`);
|
|
149
234
|
}
|
|
150
235
|
function sendParts(text, chain) {
|
|
151
|
-
const match = text.match(new RegExp(`\\b(?:send|transfer)\\s+${AMOUNT_PATTERN}\\s
|
|
236
|
+
const match = text.match(new RegExp(`\\b(?:send|transfer)\\s+${AMOUNT_PATTERN}\\s+${ASSET_PATTERN}\\s+to\\s+(0x[a-fA-F0-9]{40})\\b`, 'i'));
|
|
152
237
|
if (!match) {
|
|
153
|
-
throw new Error('Specify amount,
|
|
238
|
+
throw new Error('Specify amount, token or native asset, and recipient, for example: "send 10 USDC to 0x..."');
|
|
154
239
|
}
|
|
155
240
|
const token = match[2];
|
|
156
241
|
const normalizedToken = token
|
|
@@ -175,13 +260,39 @@ function sendParts(text, chain) {
|
|
|
175
260
|
? undefined
|
|
176
261
|
: token,
|
|
177
262
|
recipient: match[3],
|
|
263
|
+
span: matchSpan(match),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function royaltyParts(text) {
|
|
267
|
+
const mint = text.match(/\bwith\s+([0-9]+)\s*(?:bp|bps|basis\s+points?)\s+mint\s+royalt(?:y|ies)\b/i);
|
|
268
|
+
const burn = text.match(/\band\s+([0-9]+)\s*(?:bp|bps|basis\s+points?)\s+burn\s+royalt(?:y|ies)\b/i);
|
|
269
|
+
if (!mint || !burn) {
|
|
270
|
+
throw new Error('Confirmed token creation must include explicit mint and burn royalties in basis points');
|
|
271
|
+
}
|
|
272
|
+
for (const value of [mint[1], burn[1]]) {
|
|
273
|
+
if (Number(value) > 10_000) {
|
|
274
|
+
throw new Error('Royalty basis points must be between 0 and 10000');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
args: [
|
|
279
|
+
'--mint-royalty',
|
|
280
|
+
mint[1],
|
|
281
|
+
'--burn-royalty',
|
|
282
|
+
burn[1],
|
|
283
|
+
],
|
|
284
|
+
spans: [matchSpan(mint), matchSpan(burn)],
|
|
178
285
|
};
|
|
179
286
|
}
|
|
180
287
|
function createParts(text) {
|
|
181
|
-
|
|
182
|
-
const match = text.match(/\b(?:create|launch)\s+(?:a\s+)?token\s+"([^"]{1,64})"\s+\(([a-zA-Z][a-zA-Z0-9_-]*)\)\s+backed\s+by\s+(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\s+with\s+max(?:imum)?\s+supply\s+([0-9]+(?:\.[0-9]+)?)\s+using\s+(?:a\s+)?(linear|exponential|logarithmic|flat)\s+curve\s+from\s+([0-9]+(?:\.[0-9]+)?)\s+to\s+([0-9]+(?:\.[0-9]+)?)\b/i);
|
|
288
|
+
const match = text.match(/\b(?:create|launch)\s+(?:a\s+)?token\s+"([a-zA-Z0-9][a-zA-Z0-9 ._&()+-]{0,63})"\s+\(([a-zA-Z][a-zA-Z0-9_-]*)\)\s+backed\s+by\s+(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\s+with\s+max(?:imum)?\s+supply\s+([0-9]+(?:\.[0-9]+)?)\s+using\s+(?:a\s+)?(linear|exponential|logarithmic|flat)\s+curve\s+from\s+([0-9]+(?:\.[0-9]+)?)\s+to\s+([0-9]+(?:\.[0-9]+)?)\b/i);
|
|
183
289
|
if (!match) {
|
|
184
|
-
throw new Error('Use: create token "Name" (SYMBOL) backed by RESERVE with max supply N using a CURVE curve from START to END');
|
|
290
|
+
throw new Error('Use a printable ASCII name: create token "Name" (SYMBOL) backed by RESERVE with max supply N using a CURVE curve from START to END');
|
|
291
|
+
}
|
|
292
|
+
if (match[1] !== match[1].trim() ||
|
|
293
|
+
/ {2,}/.test(match[1]) ||
|
|
294
|
+
TRANSACTION_NAME_WORDS.test(match[1])) {
|
|
295
|
+
throw new Error('Token name must be printable ASCII without transaction instructions');
|
|
185
296
|
}
|
|
186
297
|
return {
|
|
187
298
|
name: match[1],
|
|
@@ -191,10 +302,23 @@ function createParts(text) {
|
|
|
191
302
|
curve: match[5].toLowerCase(),
|
|
192
303
|
initialPrice: match[6],
|
|
193
304
|
finalPrice: match[7],
|
|
305
|
+
span: matchSpan(match),
|
|
194
306
|
};
|
|
195
307
|
}
|
|
308
|
+
function confirmedWriteArgs(text, argv, spans, chainMentionCount) {
|
|
309
|
+
const confirmationSpan = confirmationEnvelopeSpan(text);
|
|
310
|
+
if (chainMentionCount !== 1) {
|
|
311
|
+
throw new Error(`Confirmed write must specify exactly one chain: ${SUPPORTED_CHAINS.join(', ')}`);
|
|
312
|
+
}
|
|
313
|
+
assertFullyParsed(text, [confirmationSpan, ...spans]);
|
|
314
|
+
return [...argv, '--yes'];
|
|
315
|
+
}
|
|
196
316
|
export function buildActionArgs(action, text) {
|
|
197
|
-
const
|
|
317
|
+
const isWrite = WRITE_ACTIONS.has(action);
|
|
318
|
+
if (isWrite)
|
|
319
|
+
confirmationEnvelopeSpan(text);
|
|
320
|
+
const chain = chainSelection(text, isWrite);
|
|
321
|
+
const prefix = chain.args;
|
|
198
322
|
switch (action) {
|
|
199
323
|
case 'TOKEN_INFO':
|
|
200
324
|
return [...prefix, 'info', tokenFromText(text, 'info')];
|
|
@@ -203,53 +327,72 @@ export function buildActionArgs(action, text) {
|
|
|
203
327
|
case 'WALLET_BALANCE':
|
|
204
328
|
return [...prefix, 'wallet'];
|
|
205
329
|
case 'BUY_TOKEN': {
|
|
206
|
-
const
|
|
207
|
-
|
|
330
|
+
const parts = tradeParts(text, 'buy');
|
|
331
|
+
const limit = tradeLimitParts(text, 'buy');
|
|
332
|
+
return confirmedWriteArgs(text, [
|
|
333
|
+
...prefix,
|
|
334
|
+
'buy',
|
|
335
|
+
parts.token,
|
|
336
|
+
'--amount',
|
|
337
|
+
parts.amount,
|
|
338
|
+
...limit.args,
|
|
339
|
+
], [...chain.spans, parts.span, limit.span], chain.mentionCount);
|
|
208
340
|
}
|
|
209
341
|
case 'SELL_TOKEN': {
|
|
210
|
-
const
|
|
211
|
-
|
|
342
|
+
const parts = tradeParts(text, 'sell');
|
|
343
|
+
const limit = tradeLimitParts(text, 'sell');
|
|
344
|
+
return confirmedWriteArgs(text, [
|
|
345
|
+
...prefix,
|
|
346
|
+
'sell',
|
|
347
|
+
parts.token,
|
|
348
|
+
'--amount',
|
|
349
|
+
parts.amount,
|
|
350
|
+
...limit.args,
|
|
351
|
+
], [...chain.spans, parts.span, limit.span], chain.mentionCount);
|
|
212
352
|
}
|
|
213
353
|
case 'ZAP_BUY': {
|
|
214
|
-
const
|
|
215
|
-
|
|
354
|
+
const parts = zapBuyParts(text);
|
|
355
|
+
const slippage = slippageParts(text);
|
|
356
|
+
return confirmedWriteArgs(text, [
|
|
216
357
|
...prefix,
|
|
217
358
|
'zap-buy',
|
|
218
|
-
token,
|
|
359
|
+
parts.token,
|
|
219
360
|
'--input-token',
|
|
220
|
-
inputToken,
|
|
361
|
+
parts.inputToken,
|
|
221
362
|
'--input-amount',
|
|
222
|
-
inputAmount,
|
|
223
|
-
...
|
|
224
|
-
];
|
|
363
|
+
parts.inputAmount,
|
|
364
|
+
...slippage.args,
|
|
365
|
+
], [...chain.spans, parts.span, ...slippage.spans], chain.mentionCount);
|
|
225
366
|
}
|
|
226
367
|
case 'ZAP_SELL': {
|
|
227
|
-
const
|
|
228
|
-
|
|
368
|
+
const parts = zapSellParts(text);
|
|
369
|
+
const slippage = slippageParts(text);
|
|
370
|
+
return confirmedWriteArgs(text, [
|
|
229
371
|
...prefix,
|
|
230
372
|
'zap-sell',
|
|
231
|
-
token,
|
|
373
|
+
parts.token,
|
|
232
374
|
'--amount',
|
|
233
|
-
amount,
|
|
375
|
+
parts.amount,
|
|
234
376
|
'--output-token',
|
|
235
|
-
outputToken,
|
|
236
|
-
...
|
|
237
|
-
];
|
|
377
|
+
parts.outputToken,
|
|
378
|
+
...slippage.args,
|
|
379
|
+
], [...chain.spans, parts.span, ...slippage.spans], chain.mentionCount);
|
|
238
380
|
}
|
|
239
381
|
case 'SEND_TOKEN': {
|
|
240
|
-
const
|
|
241
|
-
return [
|
|
382
|
+
const parts = sendParts(text, prefix[1]);
|
|
383
|
+
return confirmedWriteArgs(text, [
|
|
242
384
|
...prefix,
|
|
243
385
|
'send',
|
|
244
|
-
recipient,
|
|
386
|
+
parts.recipient,
|
|
245
387
|
'--amount',
|
|
246
|
-
amount,
|
|
247
|
-
...(token ? ['--token', token] : []),
|
|
248
|
-
];
|
|
388
|
+
parts.amount,
|
|
389
|
+
...(parts.token ? ['--token', parts.token] : []),
|
|
390
|
+
], [...chain.spans, parts.span], chain.mentionCount);
|
|
249
391
|
}
|
|
250
392
|
case 'CREATE_TOKEN': {
|
|
251
393
|
const parts = createParts(text);
|
|
252
|
-
|
|
394
|
+
const royalties = royaltyParts(text);
|
|
395
|
+
return confirmedWriteArgs(text, [
|
|
253
396
|
...prefix,
|
|
254
397
|
'create',
|
|
255
398
|
'--name',
|
|
@@ -266,8 +409,8 @@ export function buildActionArgs(action, text) {
|
|
|
266
409
|
parts.initialPrice,
|
|
267
410
|
'--final-price',
|
|
268
411
|
parts.finalPrice,
|
|
269
|
-
|
|
270
|
-
];
|
|
412
|
+
...royalties.args,
|
|
413
|
+
], [...chain.spans, parts.span, ...royalties.spans], chain.mentionCount);
|
|
271
414
|
}
|
|
272
415
|
}
|
|
273
416
|
}
|
package/dist/plugin.js
CHANGED
|
@@ -105,7 +105,7 @@ const buyTokenAction = createAction({
|
|
|
105
105
|
description: 'Mint a token with its Bond reserve ERC-20',
|
|
106
106
|
validateText: (text) => canBuildActionArgs('BUY_TOKEN', text) &&
|
|
107
107
|
!canBuildActionArgs('ZAP_BUY', text),
|
|
108
|
-
examplePrompt: 'Buy 10 SIGNET',
|
|
108
|
+
examplePrompt: 'Confirm: Buy 10 SIGNET on Base with maximum cost 12 reserve units',
|
|
109
109
|
exampleResult: 'Minted 10 SIGNET through MCV2_Bond.',
|
|
110
110
|
});
|
|
111
111
|
const sellTokenAction = createAction({
|
|
@@ -114,7 +114,7 @@ const sellTokenAction = createAction({
|
|
|
114
114
|
description: 'Burn a token for its Bond reserve ERC-20',
|
|
115
115
|
validateText: (text) => canBuildActionArgs('SELL_TOKEN', text) &&
|
|
116
116
|
!canBuildActionArgs('ZAP_SELL', text),
|
|
117
|
-
examplePrompt: 'Sell 5 SIGNET',
|
|
117
|
+
examplePrompt: 'Confirm: Sell 5 SIGNET on Base with minimum refund 4 reserve units',
|
|
118
118
|
exampleResult: 'Burned 5 SIGNET through MCV2_Bond.',
|
|
119
119
|
});
|
|
120
120
|
const zapBuyAction = createAction({
|
|
@@ -122,7 +122,7 @@ const zapBuyAction = createAction({
|
|
|
122
122
|
similes: ['BUY_WITH_ASSET', 'MINT_WITH_ASSET', 'ROUTED_MINT'],
|
|
123
123
|
description: 'Mint a Mint Club token from an exact amount of a routed native/ERC-20 asset via MCV2_ZapV2',
|
|
124
124
|
validateText: (text) => canBuildActionArgs('ZAP_BUY', text),
|
|
125
|
-
examplePrompt: 'Buy TOKEN with 10 USDC on Arbitrum',
|
|
125
|
+
examplePrompt: 'Confirm: Buy TOKEN with 10 USDC on Arbitrum with 1% slippage',
|
|
126
126
|
exampleResult: 'Minted TOKEN from 10 USDC through MCV2_ZapV2.',
|
|
127
127
|
});
|
|
128
128
|
const zapSellAction = createAction({
|
|
@@ -130,7 +130,7 @@ const zapSellAction = createAction({
|
|
|
130
130
|
similes: ['SELL_FOR_ASSET', 'BURN_TO_ASSET', 'ROUTED_BURN'],
|
|
131
131
|
description: 'Burn a Mint Club token into a routed native/ERC-20 output asset via MCV2_ZapV2',
|
|
132
132
|
validateText: (text) => canBuildActionArgs('ZAP_SELL', text),
|
|
133
|
-
examplePrompt: 'Sell 5 TOKEN for USDC on Unichain',
|
|
133
|
+
examplePrompt: 'Confirm: Sell 5 TOKEN for USDC on Unichain with 1% slippage',
|
|
134
134
|
exampleResult: 'Burned 5 TOKEN into USDC through MCV2_ZapV2.',
|
|
135
135
|
});
|
|
136
136
|
const walletBalanceAction = createAction({
|
|
@@ -146,7 +146,7 @@ const sendTokenAction = createAction({
|
|
|
146
146
|
similes: ['TRANSFER_TOKEN', 'SEND_ASSET'],
|
|
147
147
|
description: 'Send native currency or an ERC-20 token to an address',
|
|
148
148
|
validateText: (text) => canBuildActionArgs('SEND_TOKEN', text),
|
|
149
|
-
examplePrompt: 'Send 10 USDG to 0x1111111111111111111111111111111111111111 on Robinhood',
|
|
149
|
+
examplePrompt: 'Confirm: Send 10 USDG to 0x1111111111111111111111111111111111111111 on Robinhood',
|
|
150
150
|
exampleResult: 'Sent 10 USDG on Robinhood Chain.',
|
|
151
151
|
});
|
|
152
152
|
const createTokenAction = createAction({
|
|
@@ -154,7 +154,7 @@ const createTokenAction = createAction({
|
|
|
154
154
|
similes: ['LAUNCH_TOKEN', 'CREATE_BONDING_CURVE'],
|
|
155
155
|
description: 'Create an ERC-20 Mint Club V2 bonding curve token',
|
|
156
156
|
validateText: (text) => canBuildActionArgs('CREATE_TOKEN', text),
|
|
157
|
-
examplePrompt: 'Create token "My Token" (MYT) backed by USDG with max supply 1000000 using a linear curve from 0.01 to 1 on Robinhood',
|
|
157
|
+
examplePrompt: 'Confirm: Create token "My Token" (MYT) backed by USDG with max supply 1000000 using a linear curve from 0.01 to 1 on Robinhood with 100 bps mint royalty and 100 bps burn royalty',
|
|
158
158
|
exampleResult: 'Created the MYT bonding curve token on Robinhood Chain.',
|
|
159
159
|
});
|
|
160
160
|
const mintclubProvider = {
|
|
@@ -170,9 +170,10 @@ const mintclubProvider = {
|
|
|
170
170
|
'- SEND_TOKEN: send native currency or ERC-20 tokens',
|
|
171
171
|
'- CREATE_TOKEN: create an ERC-20 bonding curve token',
|
|
172
172
|
'',
|
|
173
|
-
`Supported chains: ${SUPPORTED_CHAINS.join(', ')}. Base is the default.`,
|
|
173
|
+
`Supported chains: ${SUPPORTED_CHAINS.join(', ')}. Base is the default for reads; writes require exactly one explicit chain.`,
|
|
174
174
|
'Zap routing enumerates direct and one-intermediary V2/V3/V4 candidates and uses the configured per-chain ZapV2 deployment.',
|
|
175
|
-
|
|
175
|
+
`Write actions run only when the original user message is one affirmative "Confirm:" statement that repeats the full transaction details; never add this prefix on the user's behalf. Direct trades require max-cost/min-refund reserve units, routed trades require slippage, sends require an asset, and creation requires a printable ASCII name without transaction instructions plus mint/burn royalty basis points.`,
|
|
176
|
+
'The compatible mc CLI is installed as a plugin dependency; writes also require PRIVATE_KEY or the CLI wallet file.',
|
|
176
177
|
].join('\n'),
|
|
177
178
|
values: {
|
|
178
179
|
platform: 'Mint Club V2',
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mint.club/v2-eliza-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "ElizaOS plugin for Mint Club V2 Bond operations and bounded local Uniswap ZapV2 routing across supported chains",
|
|
5
5
|
"files": [
|
|
6
|
-
"dist",
|
|
7
|
-
"README.md"
|
|
6
|
+
"dist/",
|
|
7
|
+
"README.md",
|
|
8
|
+
"LICENSE"
|
|
8
9
|
],
|
|
9
10
|
"type": "module",
|
|
10
11
|
"main": "dist/index.js",
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
"prepublishOnly": "npm run build"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@mint.club/v2-cli": "^2.0.
|
|
21
|
+
"@mint.club/v2-cli": "^2.0.2",
|
|
21
22
|
"zod": "^4.3.5"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
@@ -46,10 +47,13 @@
|
|
|
46
47
|
"@elizaos/core": "^1.7.2",
|
|
47
48
|
"@types/node": "^22.0.0",
|
|
48
49
|
"typescript": "^5.0.0",
|
|
49
|
-
"vitest": "^2.
|
|
50
|
+
"vitest": "^3.2.7"
|
|
50
51
|
},
|
|
51
52
|
"license": "MIT",
|
|
52
53
|
"publishConfig": {
|
|
53
54
|
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22.13.0"
|
|
54
58
|
}
|
|
55
59
|
}
|