@mint.club/v2-eliza-plugin 2.0.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 +88 -0
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +273 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +218 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Mint Club V2 — ElizaOS Plugin
|
|
2
|
+
|
|
3
|
+
ElizaOS actions for protocol-native [Mint Club V2](https://mint.club) operations and bounded local Uniswap ZapV2 routing across ten mainnets and two testnets.
|
|
4
|
+
|
|
5
|
+
[npm package](https://www.npmjs.com/package/@mint.club/v2-eliza-plugin) · [repository](https://github.com/Steemhunt/mint.club-v2-ai) · [CLI reference](../cli) · [MCP server](../mcp)
|
|
6
|
+
|
|
7
|
+
The plugin invokes [`@mint.club/v2-cli`](../cli) with argv arrays and does not interpolate user input into shell commands. Chain keys and aliases come from the CLI package's published `chain-registry.json`.
|
|
8
|
+
|
|
9
|
+
## Actions
|
|
10
|
+
|
|
11
|
+
The plugin exposes nine protocol-specific actions:
|
|
12
|
+
|
|
13
|
+
| Action | Description |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `TOKEN_INFO` | Token supply, reserve, price, and curve |
|
|
16
|
+
| `TOKEN_PRICE` | Token price in reserve and USD |
|
|
17
|
+
| `BUY_TOKEN` | Mint with the configured reserve ERC-20 via `MCV2_Bond` |
|
|
18
|
+
| `SELL_TOKEN` | Burn for the configured reserve ERC-20 via `MCV2_Bond` |
|
|
19
|
+
| `ZAP_BUY` | Exact routed input asset → `MCV2_ZapV2.zapMint` |
|
|
20
|
+
| `ZAP_SELL` | `MCV2_ZapV2.zapBurn` → routed output asset |
|
|
21
|
+
| `WALLET_BALANCE` | Show chain-local wallet balances |
|
|
22
|
+
| `SEND_TOKEN` | Send native currency or an ERC-20 token |
|
|
23
|
+
| `CREATE_TOKEN` | Create an ERC-20 bonding curve token |
|
|
24
|
+
|
|
25
|
+
There is no general-purpose DEX swap action. Zap routing checks only direct and one-intermediary homogeneous V2/V3/V4 paths by RPC.
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @mint.club/v2-eliza-plugin
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Node.js 18 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
|
+
|
|
35
|
+
Add the plugin to an ElizaOS character configuration:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"plugins": ["@mint.club/v2-eliza-plugin"]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
See the official [ElizaOS plugin management guide](https://docs.elizaos.ai/cli-reference/plugins) for project and character configuration details.
|
|
44
|
+
|
|
45
|
+
Read-only actions work without a wallet. Write actions and wallet balances require `PRIVATE_KEY` through the ElizaOS secret/environment configuration, or a key in `~/.mintclub/.env`. To generate a dedicated local wallet, install the CLI and run `mc wallet --generate` in a trusted terminal.
|
|
46
|
+
|
|
47
|
+
> 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
|
+
|
|
49
|
+
## Example prompts
|
|
50
|
+
|
|
51
|
+
- “Get info about SIGNET.”
|
|
52
|
+
- “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.”
|
|
57
|
+
- “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.”
|
|
60
|
+
|
|
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 defaults to 1% when omitted.
|
|
62
|
+
|
|
63
|
+
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
|
+
|
|
65
|
+
The text parser fails closed on explicit max/min or royalty constraints it cannot map without ambiguity. `CREATE_TOKEN` creates ERC-20 tokens with the CLI defaults of 100 basis points (1%) for both mint and burn royalties. Use the CLI when different royalties are required; use the CLI or MCP server for explicit trade limits.
|
|
66
|
+
|
|
67
|
+
Supported canonical chain keys:
|
|
68
|
+
|
|
69
|
+
`ethereum` · `optimism` · `arbitrum` · `avalanche` · `base` · `polygon` · `bsc` · `zora` · `unichain` · `robinhood` · `sepolia` · `base-sepolia`
|
|
70
|
+
|
|
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 multiple positive chain instructions are rejected rather than guessed.
|
|
72
|
+
|
|
73
|
+
ZapV2 is deployed on every supported chain listed above. Blast is unsupported by this integration.
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
From the repository root:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm ci
|
|
81
|
+
npm --prefix eliza-plugin run check
|
|
82
|
+
npm --prefix eliza-plugin test
|
|
83
|
+
npm --prefix eliza-plugin run build
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type MintClubActionName = 'TOKEN_INFO' | 'TOKEN_PRICE' | 'BUY_TOKEN' | 'SELL_TOKEN' | 'ZAP_BUY' | 'ZAP_SELL' | 'WALLET_BALANCE' | 'SEND_TOKEN' | 'CREATE_TOKEN';
|
|
2
|
+
export declare const SUPPORTED_CHAINS: readonly string[];
|
|
3
|
+
export declare function buildActionArgs(action: MintClubActionName, text: string): string[];
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
const chainRegistry = require('@mint.club/v2-cli/chain-registry.json');
|
|
4
|
+
export const SUPPORTED_CHAINS = Object.freeze(chainRegistry.chains.map(({ key }) => key));
|
|
5
|
+
function escapeRegExp(value) {
|
|
6
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
|
+
}
|
|
8
|
+
function normalizeChainAlias(value) {
|
|
9
|
+
return value.toLowerCase().replace(/[\s_-]+/g, '');
|
|
10
|
+
}
|
|
11
|
+
function aliasPattern(alias) {
|
|
12
|
+
return alias
|
|
13
|
+
.split(/[\s_-]+/)
|
|
14
|
+
.map(escapeRegExp)
|
|
15
|
+
.join('[\\s_-]*');
|
|
16
|
+
}
|
|
17
|
+
const chainByAlias = new Map();
|
|
18
|
+
const nativeSymbolByChain = new Map();
|
|
19
|
+
const nativeSymbols = new Set();
|
|
20
|
+
const legacyNativeChainBySymbol = new Map([
|
|
21
|
+
['MATIC', 'polygon'],
|
|
22
|
+
]);
|
|
23
|
+
const aliases = [];
|
|
24
|
+
for (const { key, nativeSymbol, aliases: configuredAliases, } of chainRegistry.chains) {
|
|
25
|
+
const normalizedNativeSymbol = nativeSymbol.toUpperCase();
|
|
26
|
+
nativeSymbolByChain.set(key, normalizedNativeSymbol);
|
|
27
|
+
nativeSymbols.add(normalizedNativeSymbol);
|
|
28
|
+
for (const alias of new Set([key, ...configuredAliases])) {
|
|
29
|
+
const normalized = normalizeChainAlias(alias);
|
|
30
|
+
const existing = chainByAlias.get(normalized);
|
|
31
|
+
if (existing && existing !== key) {
|
|
32
|
+
throw new Error(`Duplicate chain alias "${alias}" for ${existing} and ${key}`);
|
|
33
|
+
}
|
|
34
|
+
chainByAlias.set(normalized, key);
|
|
35
|
+
aliases.push(alias);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const symbol of legacyNativeChainBySymbol.keys()) {
|
|
39
|
+
nativeSymbols.add(symbol);
|
|
40
|
+
}
|
|
41
|
+
const CHAIN_ALIAS_PATTERN = [...new Set(aliases)]
|
|
42
|
+
.sort((left, right) => right.length - left.length)
|
|
43
|
+
.map(aliasPattern)
|
|
44
|
+
.join('|');
|
|
45
|
+
function resolveChainAlias(alias) {
|
|
46
|
+
const chain = chainByAlias.get(normalizeChainAlias(alias));
|
|
47
|
+
if (!chain)
|
|
48
|
+
throw new Error(`Unknown chain alias: ${alias}`);
|
|
49
|
+
return chain;
|
|
50
|
+
}
|
|
51
|
+
function chainArgs(text) {
|
|
52
|
+
const negated = new Set();
|
|
53
|
+
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, (_match, alias) => {
|
|
55
|
+
negated.add(resolveChainAlias(alias));
|
|
56
|
+
return ' ';
|
|
57
|
+
});
|
|
58
|
+
const mentioned = new Set();
|
|
59
|
+
const positive = new RegExp(`(?:\\bon\\s+(?:the\\s+)?(${CHAIN_ALIAS_PATTERN})(?:\\s+chain)?\\b|\\b(${CHAIN_ALIAS_PATTERN})\\s+(?:chain|mainnet|wallet|balance|holdings)\\b)`, 'gi');
|
|
60
|
+
for (const match of positiveText.matchAll(positive)) {
|
|
61
|
+
mentioned.add(resolveChainAlias(match[1] ?? match[2]));
|
|
62
|
+
}
|
|
63
|
+
const hasConflict = [...mentioned].some((chain) => negated.has(chain));
|
|
64
|
+
if (mentioned.size > 1 ||
|
|
65
|
+
hasConflict ||
|
|
66
|
+
(mentioned.size === 0 && negated.has('base'))) {
|
|
67
|
+
throw new Error(`Specify exactly one chain: ${SUPPORTED_CHAINS.join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
const selected = mentioned.values().next().value ?? 'base';
|
|
70
|
+
return ['--chain', selected];
|
|
71
|
+
}
|
|
72
|
+
const TOKEN_PATTERN = '(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)';
|
|
73
|
+
const ASSET_PATTERN = '(native(?:\\s+currency)?|0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)';
|
|
74
|
+
const AMOUNT_PATTERN = '([0-9]+(?:\\.[0-9]+)?)';
|
|
75
|
+
function normalizeAsset(value) {
|
|
76
|
+
return /^native(?:\s+currency)?$/i.test(value) ? 'NATIVE' : value;
|
|
77
|
+
}
|
|
78
|
+
function rejectUnsupportedIntent(text, pattern, description) {
|
|
79
|
+
if (pattern.test(text)) {
|
|
80
|
+
throw new Error(`${description} is not supported by this text parser`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
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
|
+
const verbs = action === 'buy' ? '(?:buy|mint)' : '(?:sell|burn)';
|
|
88
|
+
const match = text.match(new RegExp(`\\b${verbs}\\s+${AMOUNT_PATTERN}\\s+(?:of\\s+)?${TOKEN_PATTERN}\\b`, 'i'));
|
|
89
|
+
if (!match) {
|
|
90
|
+
throw new Error('Specify an amount and token, for example: "buy 10 SIGNET"');
|
|
91
|
+
}
|
|
92
|
+
const trailingText = text.slice((match.index ?? 0) + match[0].length);
|
|
93
|
+
const routedIntent = action === 'buy'
|
|
94
|
+
? /\b(?:with|using|from)\b/i
|
|
95
|
+
: /\b(?:for|to|into)\b/i;
|
|
96
|
+
if (routedIntent.test(trailingText)) {
|
|
97
|
+
throw new Error(action === 'buy'
|
|
98
|
+
? 'Use the exact-input form: Buy TOKEN with AMOUNT INPUT_TOKEN'
|
|
99
|
+
: 'Use the routed-output form: Sell AMOUNT TOKEN for OUTPUT_TOKEN');
|
|
100
|
+
}
|
|
101
|
+
return { amount: match[1], token: match[2] };
|
|
102
|
+
}
|
|
103
|
+
function zapBuyParts(text) {
|
|
104
|
+
rejectUnsupportedIntent(text, /\bmin(?:imum)?\s+tokens?\b/i, 'Minimum token output');
|
|
105
|
+
const match = text.match(new RegExp(`\\b(?:zap\\s+)?(?:buy|mint)\\s+(?:token\\s+)?${TOKEN_PATTERN}\\s+(?:with|using|from)\\s+${AMOUNT_PATTERN}\\s+(?:of\\s+)?${ASSET_PATTERN}\\b`, 'i'));
|
|
106
|
+
if (!match) {
|
|
107
|
+
throw new Error('Use: Buy TOKEN with AMOUNT INPUT_TOKEN, for example: "Buy SIGNET with 10 USDC"');
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
token: match[1],
|
|
111
|
+
inputAmount: match[2],
|
|
112
|
+
inputToken: normalizeAsset(match[3]),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function zapSellParts(text) {
|
|
116
|
+
rejectUnsupportedIntent(text, /\bmin(?:imum)?\s+output\b/i, 'Minimum routed output');
|
|
117
|
+
const match = text.match(new RegExp(`\\b(?:zap\\s+)?(?:sell|burn)\\s+${AMOUNT_PATTERN}\\s+(?:of\\s+)?${TOKEN_PATTERN}\\s+(?:for|to|into)\\s+${ASSET_PATTERN}\\b`, 'i'));
|
|
118
|
+
if (!match) {
|
|
119
|
+
throw new Error('Use: Sell AMOUNT TOKEN for OUTPUT_TOKEN, for example: "Sell 5 SIGNET for USDC"');
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
amount: match[1],
|
|
123
|
+
token: match[2],
|
|
124
|
+
outputToken: normalizeAsset(match[3]),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function slippageArgs(text) {
|
|
128
|
+
const match = text.match(/\b(?:with|at)?\s*([0-9]+(?:\.[0-9]+)?)\s*%\s+slippage\b/i);
|
|
129
|
+
return match ? ['--slippage', match[1]] : [];
|
|
130
|
+
}
|
|
131
|
+
function tokenFromText(text, action) {
|
|
132
|
+
const patterns = action === 'info'
|
|
133
|
+
? [
|
|
134
|
+
/\b(?:info|details)\s+(?:about|for|of)?\s*(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\b/i,
|
|
135
|
+
/\babout\s+(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\b/i,
|
|
136
|
+
]
|
|
137
|
+
: [
|
|
138
|
+
/\bprice\s+(?:of|for)?\s*(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\b/i,
|
|
139
|
+
/\bworth\s+(?:of\s+)?(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\b/i,
|
|
140
|
+
/\bwhat\s+is\s+(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\s+worth\b/i,
|
|
141
|
+
/\b(0x[a-fA-F0-9]{40}|[a-zA-Z][a-zA-Z0-9_-]*)\s+(?:price|worth)\b/i,
|
|
142
|
+
];
|
|
143
|
+
for (const pattern of patterns) {
|
|
144
|
+
const match = text.match(pattern);
|
|
145
|
+
if (match)
|
|
146
|
+
return match[1];
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`Specify a token for ${action}`);
|
|
149
|
+
}
|
|
150
|
+
function sendParts(text, chain) {
|
|
151
|
+
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
|
+
if (!match) {
|
|
153
|
+
throw new Error('Specify amount, optional token, and recipient, for example: "send 10 USDC to 0x..."');
|
|
154
|
+
}
|
|
155
|
+
const token = match[2];
|
|
156
|
+
const normalizedToken = token
|
|
157
|
+
? normalizeAsset(token).toUpperCase()
|
|
158
|
+
: undefined;
|
|
159
|
+
const nativeSymbol = nativeSymbolByChain.get(chain);
|
|
160
|
+
if (!nativeSymbol)
|
|
161
|
+
throw new Error(`Missing native symbol for ${chain}`);
|
|
162
|
+
const isSelectedNativeSymbol = normalizedToken === nativeSymbol ||
|
|
163
|
+
legacyNativeChainBySymbol.get(normalizedToken ?? '') === chain;
|
|
164
|
+
if (normalizedToken &&
|
|
165
|
+
normalizedToken !== 'NATIVE' &&
|
|
166
|
+
nativeSymbols.has(normalizedToken) &&
|
|
167
|
+
!isSelectedNativeSymbol) {
|
|
168
|
+
throw new Error(`${normalizedToken} is not the native currency on ${chain}; use ${nativeSymbol} or NATIVE`);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
amount: match[1],
|
|
172
|
+
token: normalizedToken === undefined ||
|
|
173
|
+
normalizedToken === 'NATIVE' ||
|
|
174
|
+
isSelectedNativeSymbol
|
|
175
|
+
? undefined
|
|
176
|
+
: token,
|
|
177
|
+
recipient: match[3],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function createParts(text) {
|
|
181
|
+
rejectUnsupportedIntent(text, /\b(?:(?:mint|burn)\s+)?royalt(?:y|ies)\b/i, 'Explicit royalty configuration');
|
|
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);
|
|
183
|
+
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');
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
name: match[1],
|
|
188
|
+
symbol: match[2],
|
|
189
|
+
reserve: match[3],
|
|
190
|
+
maxSupply: match[4],
|
|
191
|
+
curve: match[5].toLowerCase(),
|
|
192
|
+
initialPrice: match[6],
|
|
193
|
+
finalPrice: match[7],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export function buildActionArgs(action, text) {
|
|
197
|
+
const prefix = chainArgs(text);
|
|
198
|
+
switch (action) {
|
|
199
|
+
case 'TOKEN_INFO':
|
|
200
|
+
return [...prefix, 'info', tokenFromText(text, 'info')];
|
|
201
|
+
case 'TOKEN_PRICE':
|
|
202
|
+
return [...prefix, 'price', tokenFromText(text, 'price')];
|
|
203
|
+
case 'WALLET_BALANCE':
|
|
204
|
+
return [...prefix, 'wallet'];
|
|
205
|
+
case 'BUY_TOKEN': {
|
|
206
|
+
const { token, amount } = tradeParts(text, 'buy');
|
|
207
|
+
return [...prefix, 'buy', token, '--amount', amount];
|
|
208
|
+
}
|
|
209
|
+
case 'SELL_TOKEN': {
|
|
210
|
+
const { token, amount } = tradeParts(text, 'sell');
|
|
211
|
+
return [...prefix, 'sell', token, '--amount', amount];
|
|
212
|
+
}
|
|
213
|
+
case 'ZAP_BUY': {
|
|
214
|
+
const { token, inputAmount, inputToken } = zapBuyParts(text);
|
|
215
|
+
return [
|
|
216
|
+
...prefix,
|
|
217
|
+
'zap-buy',
|
|
218
|
+
token,
|
|
219
|
+
'--input-token',
|
|
220
|
+
inputToken,
|
|
221
|
+
'--input-amount',
|
|
222
|
+
inputAmount,
|
|
223
|
+
...slippageArgs(text),
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
case 'ZAP_SELL': {
|
|
227
|
+
const { token, amount, outputToken } = zapSellParts(text);
|
|
228
|
+
return [
|
|
229
|
+
...prefix,
|
|
230
|
+
'zap-sell',
|
|
231
|
+
token,
|
|
232
|
+
'--amount',
|
|
233
|
+
amount,
|
|
234
|
+
'--output-token',
|
|
235
|
+
outputToken,
|
|
236
|
+
...slippageArgs(text),
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
case 'SEND_TOKEN': {
|
|
240
|
+
const { amount, token, recipient } = sendParts(text, prefix[1]);
|
|
241
|
+
return [
|
|
242
|
+
...prefix,
|
|
243
|
+
'send',
|
|
244
|
+
recipient,
|
|
245
|
+
'--amount',
|
|
246
|
+
amount,
|
|
247
|
+
...(token ? ['--token', token] : []),
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
case 'CREATE_TOKEN': {
|
|
251
|
+
const parts = createParts(text);
|
|
252
|
+
return [
|
|
253
|
+
...prefix,
|
|
254
|
+
'create',
|
|
255
|
+
'--name',
|
|
256
|
+
parts.name,
|
|
257
|
+
'--symbol',
|
|
258
|
+
parts.symbol,
|
|
259
|
+
'--reserve',
|
|
260
|
+
parts.reserve,
|
|
261
|
+
'--max-supply',
|
|
262
|
+
parts.maxSupply,
|
|
263
|
+
'--curve',
|
|
264
|
+
parts.curve,
|
|
265
|
+
'--initial-price',
|
|
266
|
+
parts.initialPrice,
|
|
267
|
+
'--final-price',
|
|
268
|
+
parts.finalPrice,
|
|
269
|
+
'--yes',
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { logger } from '@elizaos/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import { SUPPORTED_CHAINS, buildActionArgs, } from './commands.js';
|
|
6
|
+
const configSchema = z.object({
|
|
7
|
+
PRIVATE_KEY: z.string().min(1).optional(),
|
|
8
|
+
});
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
export function resolveCliInvocation(argv) {
|
|
11
|
+
const override = process.env.MINTCLUB_CLI;
|
|
12
|
+
if (override)
|
|
13
|
+
return { command: override, args: argv };
|
|
14
|
+
try {
|
|
15
|
+
const cliEntrypoint = require.resolve('@mint.club/v2-cli');
|
|
16
|
+
return { command: process.execPath, args: [cliEntrypoint, ...argv] };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error('@mint.club/v2-cli 2.x is not installed; reinstall @mint.club/v2-eliza-plugin with production dependencies');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function runMcCommand(argv) {
|
|
23
|
+
try {
|
|
24
|
+
const invocation = resolveCliInvocation(argv);
|
|
25
|
+
return execFileSync(invocation.command, invocation.args, {
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
timeout: 300_000,
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
}).trim();
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const failure = error;
|
|
33
|
+
const stderr = failure.stderr?.toString().trim();
|
|
34
|
+
throw new Error(`mc command failed: ${stderr || failure.message || 'unknown error'}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function createAction(spec) {
|
|
38
|
+
return {
|
|
39
|
+
name: spec.name,
|
|
40
|
+
similes: spec.similes,
|
|
41
|
+
description: spec.description,
|
|
42
|
+
validate: async (_runtime, message, _state) => spec.validateText(message.content.text || ''),
|
|
43
|
+
handler: async (_runtime, message, _state, _options, callback) => {
|
|
44
|
+
try {
|
|
45
|
+
const result = runMcCommand(buildActionArgs(spec.name, message.content.text || ''));
|
|
46
|
+
if (callback) {
|
|
47
|
+
await callback({
|
|
48
|
+
text: result,
|
|
49
|
+
actions: [spec.name],
|
|
50
|
+
source: message.content.source,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { text: result, success: true };
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
logger.error({ error }, `Error in ${spec.name} action`);
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
examples: [
|
|
64
|
+
[
|
|
65
|
+
{
|
|
66
|
+
name: '{{userName}}',
|
|
67
|
+
content: { text: spec.examplePrompt, actions: [] },
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: '{{agentName}}',
|
|
71
|
+
content: { text: spec.exampleResult, actions: [spec.name] },
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function canBuildActionArgs(name, text) {
|
|
78
|
+
try {
|
|
79
|
+
buildActionArgs(name, text);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const tokenInfoAction = createAction({
|
|
87
|
+
name: 'TOKEN_INFO',
|
|
88
|
+
similes: ['GET_TOKEN_INFO', 'MINT_CLUB_INFO', 'MC_INFO'],
|
|
89
|
+
description: 'Get information about a Mint Club V2 token',
|
|
90
|
+
validateText: (text) => /\b(info|details|about)\b/i.test(text),
|
|
91
|
+
examplePrompt: 'Get info about SIGNET',
|
|
92
|
+
exampleResult: 'Here is the token information for SIGNET.',
|
|
93
|
+
});
|
|
94
|
+
const tokenPriceAction = createAction({
|
|
95
|
+
name: 'TOKEN_PRICE',
|
|
96
|
+
similes: ['GET_TOKEN_PRICE', 'MINT_CLUB_PRICE', 'MC_PRICE'],
|
|
97
|
+
description: 'Get a Mint Club V2 token price in reserve and USD',
|
|
98
|
+
validateText: (text) => canBuildActionArgs('TOKEN_PRICE', text),
|
|
99
|
+
examplePrompt: 'What is the price of SIGNET?',
|
|
100
|
+
exampleResult: 'Here is the current SIGNET price.',
|
|
101
|
+
});
|
|
102
|
+
const buyTokenAction = createAction({
|
|
103
|
+
name: 'BUY_TOKEN',
|
|
104
|
+
similes: ['MINT_TOKEN', 'BOND_BUY'],
|
|
105
|
+
description: 'Mint a token with its Bond reserve ERC-20',
|
|
106
|
+
validateText: (text) => canBuildActionArgs('BUY_TOKEN', text) &&
|
|
107
|
+
!canBuildActionArgs('ZAP_BUY', text),
|
|
108
|
+
examplePrompt: 'Buy 10 SIGNET',
|
|
109
|
+
exampleResult: 'Minted 10 SIGNET through MCV2_Bond.',
|
|
110
|
+
});
|
|
111
|
+
const sellTokenAction = createAction({
|
|
112
|
+
name: 'SELL_TOKEN',
|
|
113
|
+
similes: ['BURN_TOKEN', 'BOND_SELL'],
|
|
114
|
+
description: 'Burn a token for its Bond reserve ERC-20',
|
|
115
|
+
validateText: (text) => canBuildActionArgs('SELL_TOKEN', text) &&
|
|
116
|
+
!canBuildActionArgs('ZAP_SELL', text),
|
|
117
|
+
examplePrompt: 'Sell 5 SIGNET',
|
|
118
|
+
exampleResult: 'Burned 5 SIGNET through MCV2_Bond.',
|
|
119
|
+
});
|
|
120
|
+
const zapBuyAction = createAction({
|
|
121
|
+
name: 'ZAP_BUY',
|
|
122
|
+
similes: ['BUY_WITH_ASSET', 'MINT_WITH_ASSET', 'ROUTED_MINT'],
|
|
123
|
+
description: 'Mint a Mint Club token from an exact amount of a routed native/ERC-20 asset via MCV2_ZapV2',
|
|
124
|
+
validateText: (text) => canBuildActionArgs('ZAP_BUY', text),
|
|
125
|
+
examplePrompt: 'Buy TOKEN with 10 USDC on Arbitrum',
|
|
126
|
+
exampleResult: 'Minted TOKEN from 10 USDC through MCV2_ZapV2.',
|
|
127
|
+
});
|
|
128
|
+
const zapSellAction = createAction({
|
|
129
|
+
name: 'ZAP_SELL',
|
|
130
|
+
similes: ['SELL_FOR_ASSET', 'BURN_TO_ASSET', 'ROUTED_BURN'],
|
|
131
|
+
description: 'Burn a Mint Club token into a routed native/ERC-20 output asset via MCV2_ZapV2',
|
|
132
|
+
validateText: (text) => canBuildActionArgs('ZAP_SELL', text),
|
|
133
|
+
examplePrompt: 'Sell 5 TOKEN for USDC on Unichain',
|
|
134
|
+
exampleResult: 'Burned 5 TOKEN into USDC through MCV2_ZapV2.',
|
|
135
|
+
});
|
|
136
|
+
const walletBalanceAction = createAction({
|
|
137
|
+
name: 'WALLET_BALANCE',
|
|
138
|
+
similes: ['CHECK_BALANCE', 'MY_WALLET', 'MC_WALLET'],
|
|
139
|
+
description: 'Show chain-local balances for the configured wallet',
|
|
140
|
+
validateText: (text) => /\b(wallet|balance|holdings)\b/i.test(text),
|
|
141
|
+
examplePrompt: 'Show my wallet balance on Robinhood',
|
|
142
|
+
exampleResult: 'Here are your Robinhood Chain balances.',
|
|
143
|
+
});
|
|
144
|
+
const sendTokenAction = createAction({
|
|
145
|
+
name: 'SEND_TOKEN',
|
|
146
|
+
similes: ['TRANSFER_TOKEN', 'SEND_ASSET'],
|
|
147
|
+
description: 'Send native currency or an ERC-20 token to an address',
|
|
148
|
+
validateText: (text) => canBuildActionArgs('SEND_TOKEN', text),
|
|
149
|
+
examplePrompt: 'Send 10 USDG to 0x1111111111111111111111111111111111111111 on Robinhood',
|
|
150
|
+
exampleResult: 'Sent 10 USDG on Robinhood Chain.',
|
|
151
|
+
});
|
|
152
|
+
const createTokenAction = createAction({
|
|
153
|
+
name: 'CREATE_TOKEN',
|
|
154
|
+
similes: ['LAUNCH_TOKEN', 'CREATE_BONDING_CURVE'],
|
|
155
|
+
description: 'Create an ERC-20 Mint Club V2 bonding curve token',
|
|
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',
|
|
158
|
+
exampleResult: 'Created the MYT bonding curve token on Robinhood Chain.',
|
|
159
|
+
});
|
|
160
|
+
const mintclubProvider = {
|
|
161
|
+
name: 'MINTCLUB_PROVIDER',
|
|
162
|
+
description: 'Provides context about Mint Club V2 protocol operations across supported Uniswap chains',
|
|
163
|
+
get: async (_runtime, _message, _state) => ({
|
|
164
|
+
text: [
|
|
165
|
+
'Mint Club V2 bonding curve actions:',
|
|
166
|
+
'- TOKEN_INFO / TOKEN_PRICE: read token data',
|
|
167
|
+
'- BUY_TOKEN / SELL_TOKEN: MCV2_Bond mint and burn of ERC-20/1155 targets with reserve ERC-20',
|
|
168
|
+
'- ZAP_BUY / ZAP_SELL: MCV2_ZapV2 exact-input local Uniswap routing for native/ERC-20 assets',
|
|
169
|
+
'- WALLET_BALANCE: show chain-local wallet balances',
|
|
170
|
+
'- SEND_TOKEN: send native currency or ERC-20 tokens',
|
|
171
|
+
'- CREATE_TOKEN: create an ERC-20 bonding curve token',
|
|
172
|
+
'',
|
|
173
|
+
`Supported chains: ${SUPPORTED_CHAINS.join(', ')}. Base is the default.`,
|
|
174
|
+
'Zap routing enumerates direct and one-intermediary V2/V3/V4 candidates and uses the configured per-chain ZapV2 deployment.',
|
|
175
|
+
'The compatible mc CLI is installed as a plugin dependency; write actions require PRIVATE_KEY or the CLI wallet file.',
|
|
176
|
+
].join('\n'),
|
|
177
|
+
values: {
|
|
178
|
+
platform: 'Mint Club V2',
|
|
179
|
+
cli: '@mint.club/v2-cli',
|
|
180
|
+
chains: SUPPORTED_CHAINS.join(','),
|
|
181
|
+
},
|
|
182
|
+
data: {},
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
export const mintclubPlugin = {
|
|
186
|
+
name: 'plugin-mintclub',
|
|
187
|
+
description: 'Mint Club V2 plugin for bonding curves and bounded local Uniswap ZapV2 routing across supported chains',
|
|
188
|
+
config: { PRIVATE_KEY: process.env.PRIVATE_KEY },
|
|
189
|
+
async init(config) {
|
|
190
|
+
logger.info('Initializing Mint Club V2 plugin');
|
|
191
|
+
try {
|
|
192
|
+
const validatedConfig = await configSchema.parseAsync(config);
|
|
193
|
+
if (validatedConfig.PRIVATE_KEY) {
|
|
194
|
+
process.env.PRIVATE_KEY = validatedConfig.PRIVATE_KEY;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof z.ZodError) {
|
|
199
|
+
const messages = error.issues.map((issue) => issue.message).join(', ');
|
|
200
|
+
throw new Error(`Invalid plugin configuration: ${messages}`);
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
actions: [
|
|
206
|
+
tokenInfoAction,
|
|
207
|
+
tokenPriceAction,
|
|
208
|
+
buyTokenAction,
|
|
209
|
+
sellTokenAction,
|
|
210
|
+
zapBuyAction,
|
|
211
|
+
zapSellAction,
|
|
212
|
+
walletBalanceAction,
|
|
213
|
+
sendTokenAction,
|
|
214
|
+
createTokenAction,
|
|
215
|
+
],
|
|
216
|
+
providers: [mintclubProvider],
|
|
217
|
+
};
|
|
218
|
+
export default mintclubPlugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mint.club/v2-eliza-plugin",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "ElizaOS plugin for Mint Club V2 Bond operations and bounded local Uniswap ZapV2 routing across supported chains",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"README.md"
|
|
8
|
+
],
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"check": "tsc --noEmit",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@mint.club/v2-cli": "^2.0.0",
|
|
21
|
+
"zod": "^4.3.5"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@elizaos/core": "^1.7.2"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"elizaos",
|
|
28
|
+
"mint-club",
|
|
29
|
+
"bonding-curve",
|
|
30
|
+
"uniswap",
|
|
31
|
+
"zap-v2",
|
|
32
|
+
"multi-chain",
|
|
33
|
+
"defi",
|
|
34
|
+
"ai"
|
|
35
|
+
],
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/Steemhunt/mint.club-v2-ai.git",
|
|
39
|
+
"directory": "eliza-plugin"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/Steemhunt/mint.club-v2-ai/tree/main/eliza-plugin",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/Steemhunt/mint.club-v2-ai/issues"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@elizaos/core": "^1.7.2",
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"vitest": "^2.1.9"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|