@goplausible/openclaw-algorand-plugin 1.7.2 → 1.8.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 +20 -4
- package/index.ts +33 -0
- package/lib/mcp-servers.ts +1 -1
- package/memory/algorand-plugin.md +48 -10
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/backup-keyring.js +117 -0
- package/scripts/setup-keyring.sh +428 -0
- package/setup.ts +14 -30
- package/skills/algorand-interaction/SKILL.md +28 -3
- package/skills/algorand-interaction/references/algorand-mcp.md +25 -5
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +3 -3
package/README.md
CHANGED
|
@@ -4,16 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Local MCP Server**: Bundled `algorand-mcp` (
|
|
7
|
+
- **Local MCP Server**: Bundled `algorand-mcp` (107 tools) — wallet, transactions, smart contracts, TEAL, indexer, DEX, NFD, Haystack Router, Alpha Arcade, knowledge base
|
|
8
8
|
- **x402 Payment Protocol**: Built-in `x402_fetch` tool for HTTP-native payments on Algorand
|
|
9
|
+
- **Headless Linux Keyring Persistence**: Automatic setup for wallet key persistence on cloud VMs and Docker (survives reboots)
|
|
9
10
|
- **Interactive Setup**: Guided wizard for configuration
|
|
10
|
-
- **Skills Included** (
|
|
11
|
+
- **Skills Included** (9 skills):
|
|
11
12
|
- `algorand-development` — AlgoKit CLI, project creation, general workflows
|
|
12
13
|
- `algorand-typescript` — TypeScript smart contracts (PuyaTs)
|
|
13
14
|
- `algorand-python` — Python smart contracts (PuyaPy)
|
|
14
15
|
- `algorand-interaction` — Blockchain interaction via MCP (wallet, transactions, swaps, NFD)
|
|
15
16
|
- `algorand-x402-typescript` — x402 payments in TypeScript
|
|
16
17
|
- `algorand-x402-python` — x402 payments in Python
|
|
18
|
+
- `algorand-x402-payment` — Runtime x402 payment (agent as client)
|
|
19
|
+
- `haystack-router-development` — DEX aggregator SDK integration
|
|
20
|
+
- `haystack-router-interaction` — Best-price swaps via MCP tools
|
|
21
|
+
- `alpha-arcade-interaction` — Prediction markets interaction
|
|
17
22
|
|
|
18
23
|
## Installation
|
|
19
24
|
|
|
@@ -35,7 +40,7 @@ After installing, run these commands:
|
|
|
35
40
|
# 1. Initialize plugin (write memory file + configure mcporter)
|
|
36
41
|
openclaw algorand-plugin init
|
|
37
42
|
|
|
38
|
-
# 2. Run interactive setup (
|
|
43
|
+
# 2. Run interactive setup (keyring persistence + x402 toggle)
|
|
39
44
|
openclaw algorand-plugin setup
|
|
40
45
|
|
|
41
46
|
# 3. Restart the gateway
|
|
@@ -55,7 +60,7 @@ openclaw algorand-plugin mcp-config # Show MCP config snippet for external codi
|
|
|
55
60
|
|
|
56
61
|
The plugin bundles [`@goplausible/algorand-mcp`](https://www.npmjs.com/package/@goplausible/algorand-mcp) as an npm dependency. It runs locally via stdio through [mcporter](https://www.npmjs.com/package/mcporter).
|
|
57
62
|
|
|
58
|
-
- **
|
|
63
|
+
- **107 tools** across 13 categories (wallet, transactions, algod, indexer, NFD, Tinyman, Haystack Router, Pera verification, Alpha Arcade, TEAL, knowledge base, and more)
|
|
59
64
|
- **Multi-network**: `mainnet`, `testnet`, `localnet`
|
|
60
65
|
- **Secure wallet**: Per-transaction and daily spending limits, private keys never exposed to agents
|
|
61
66
|
|
|
@@ -67,6 +72,17 @@ When `enableX402` is enabled (default), the plugin registers the `x402_fetch` to
|
|
|
67
72
|
- Agent builds payment using algorand-mcp wallet tools (atomic group with facilitator-sponsored fees)
|
|
68
73
|
- Agent retries with signed `PAYMENT-SIGNATURE` header to complete the payment and access the resource
|
|
69
74
|
|
|
75
|
+
## Headless Linux (Cloud VMs, Docker)
|
|
76
|
+
|
|
77
|
+
On headless Linux, the OS keyring defaults to in-memory storage — wallet keys created by `algorand-mcp` are **lost on reboot**. The setup wizard (`openclaw algorand-plugin setup`) automatically detects this and:
|
|
78
|
+
|
|
79
|
+
1. Installs `gnome-keyring`, `libsecret-tools`, `dbus-user-session`
|
|
80
|
+
2. Enables `loginctl linger` for D-Bus session persistence
|
|
81
|
+
3. Creates a persistent login keyring (auto-unlocked, no password)
|
|
82
|
+
4. Backs up and restores existing wallet mnemonics if upgrading
|
|
83
|
+
|
|
84
|
+
After setup, wallet keys persist across reboots with no user interaction needed. Agent wallets are hot wallets — keep funds minimal and use QR code top-ups.
|
|
85
|
+
|
|
70
86
|
## Configuration
|
|
71
87
|
|
|
72
88
|
Config is stored in `~/.openclaw/openclaw.json` under `plugins.entries.openclaw-algorand-plugin.config`:
|
package/index.ts
CHANGED
|
@@ -301,6 +301,29 @@ export default function register(api: PluginApi) {
|
|
|
301
301
|
console.log(" Config:");
|
|
302
302
|
console.log(` x402: ${pluginConfig.enableX402 !== false ? "Enabled" : "Disabled"}`);
|
|
303
303
|
console.log("");
|
|
304
|
+
|
|
305
|
+
// Keyring status
|
|
306
|
+
try {
|
|
307
|
+
const scriptPath = join(__dirname, "scripts", "setup-keyring.sh");
|
|
308
|
+
const keyringOutput = execSync(`bash "${scriptPath}" --detect`, { encoding: "utf-8", timeout: 5000 });
|
|
309
|
+
const vars = Object.fromEntries(
|
|
310
|
+
keyringOutput.trim().split("\n").map((line: string) => line.split("=", 2))
|
|
311
|
+
);
|
|
312
|
+
console.log(" Keyring:");
|
|
313
|
+
if (vars.PERSISTENT === "true") {
|
|
314
|
+
console.log(` Storage: ✅ ${vars.BACKEND}`);
|
|
315
|
+
console.log(` Wallets: ${vars.WALLET_DB_COUNT} account(s) in wallet.db`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(` Storage: ⚠️ ${vars.BACKEND} — run \`openclaw algorand-plugin setup\``);
|
|
318
|
+
if (parseInt(vars.WALLET_DB_COUNT) > 0) {
|
|
319
|
+
console.log(` Wallets: ⚠️ ${vars.WALLET_DB_COUNT} account(s) with mnemonics in volatile storage!`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
console.log(" Keyring:");
|
|
324
|
+
console.log(" Storage: ❓ Could not detect");
|
|
325
|
+
}
|
|
326
|
+
console.log("");
|
|
304
327
|
console.log(" Links:");
|
|
305
328
|
console.log(` GoPlausible: ${GOPLAUSIBLE_SERVICES.website}`);
|
|
306
329
|
console.log(` Algorand x402: ${GOPLAUSIBLE_SERVICES.x402}`);
|
|
@@ -346,6 +369,16 @@ export default function register(api: PluginApi) {
|
|
|
346
369
|
console.log(" This plugin provides:");
|
|
347
370
|
console.log(" • 9 Algorand skills (Algorand development in TS and Python, x402, MCP interaction, alpha arcade interaction, haystack router development and interaction)");
|
|
348
371
|
console.log(" • algorand-mcp server (~100 blockchain tools via mcporter)\n");
|
|
372
|
+
// Keyring persistence warning for headless Linux
|
|
373
|
+
try {
|
|
374
|
+
const scriptPath = join(__dirname, "scripts", "setup-keyring.sh");
|
|
375
|
+
const keyringOutput = execSync(`bash "${scriptPath}" --detect`, { encoding: "utf-8", timeout: 5000 });
|
|
376
|
+
if (keyringOutput.includes("PERSISTENT=false")) {
|
|
377
|
+
console.log(" ⚠️ Headless Linux detected — wallet keys use in-memory storage.");
|
|
378
|
+
console.log(" Run `openclaw algorand-plugin setup` for persistent storage.\n");
|
|
379
|
+
}
|
|
380
|
+
} catch { /* ignore on install */ }
|
|
381
|
+
|
|
349
382
|
console.log(" Next steps:");
|
|
350
383
|
console.log(" 1. Run `openclaw algorand-plugin init` — configure mcporter + add plugin memory");
|
|
351
384
|
console.log(" 2. Run `openclaw algorand-plugin setup` — configure options & add to allow list");
|
package/lib/mcp-servers.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const ALGORAND_MCP = {
|
|
2
2
|
id: "algorand-mcp",
|
|
3
3
|
name: "Algorand MCP",
|
|
4
|
-
description: "Local Algorand MCP server —
|
|
4
|
+
description: "Local Algorand MCP server — 107 tools for blockchain interaction",
|
|
5
5
|
type: "stdio" as const,
|
|
6
6
|
command: "algorand-mcp",
|
|
7
7
|
} as const;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# Algorand Plugin Guide
|
|
2
2
|
|
|
3
|
-
This plugin enables
|
|
3
|
+
This plugin enables four core capabilities:
|
|
4
4
|
|
|
5
5
|
1. **Algorand Development** — Smart contracts, typed clients, React frontends via AlgoKit CLI and skills
|
|
6
|
-
2. **Blockchain Interaction** — Algorand MCP server (
|
|
6
|
+
2. **Blockchain Interaction** — Algorand MCP server (107 tools) via mcporter (includes Pera asset verification)
|
|
7
7
|
3. **x402 Payment Protocol** — HTTP-native payments with Algorand as first-class chain
|
|
8
|
+
4. **Haystack Router** — DEX aggregator/smart order routing on Algorand (Tinyman V2, Pact, Folks)
|
|
9
|
+
5. **Alpha Arcade** — On-chain prediction markets on Algorand (USDC-denominated, binary/multi-choice)
|
|
8
10
|
|
|
9
11
|
## Skill Routing
|
|
10
12
|
|
|
@@ -16,6 +18,10 @@ This plugin enables three core capabilities:
|
|
|
16
18
|
| Interaction | Blockchain interaction via MCP | `algorand-interaction` |
|
|
17
19
|
| x402 | TypeScript x402 development | `algorand-x402-typescript` |
|
|
18
20
|
| x402 | Python x402 development | `algorand-x402-python` |
|
|
21
|
+
| x402 | Runtime x402 payment (Claude as client) | `algorand-x402-payment` |
|
|
22
|
+
| Haystack | Build swap apps with SDK | `haystack-router-development` |
|
|
23
|
+
| Haystack | Execute swaps via MCP tools | `haystack-router-interaction` |
|
|
24
|
+
| Alpha Arcade | Prediction markets via MCP tools | `alpha-arcade-interaction` |
|
|
19
25
|
|
|
20
26
|
## Using Algorand MCP Tools
|
|
21
27
|
|
|
@@ -31,7 +37,7 @@ mcporter call algorand-mcp.get_account_info address=XXXXX network=testnet
|
|
|
31
37
|
mcporter call algorand-mcp.search_assets name=USDC network=mainnet
|
|
32
38
|
```
|
|
33
39
|
|
|
34
|
-
## MCP Tool Categories (
|
|
40
|
+
## MCP Tool Categories (107 tools)
|
|
35
41
|
|
|
36
42
|
- **Wallet** (10) — `wallet_add_account`, `wallet_remove_account`, `wallet_list_accounts`, `wallet_switch_account`, `wallet_get_info`, `wallet_get_assets`, `wallet_sign_transaction`, `wallet_sign_transaction_group`, `wallet_sign_data`, `wallet_optin_asset`
|
|
37
43
|
- **Account Management** (8) — `create_account`, `rekey_account`, `mnemonic_to_mdk`, `mdk_to_mnemonic`, `secret_key_to_mnemonic`, `mnemonic_to_secret_key`, `seed_from_mnemonic`, `mnemonic_from_seed`
|
|
@@ -42,9 +48,37 @@ mcporter call algorand-mcp.search_assets name=USDC network=mainnet
|
|
|
42
48
|
- **Indexer API** (17) — `api_indexer_lookup_account_by_id`, `api_indexer_lookup_account_assets`, `api_indexer_lookup_account_app_local_states`, `api_indexer_lookup_account_created_applications`, `api_indexer_search_for_accounts`, `api_indexer_lookup_applications`, `api_indexer_lookup_application_logs`, `api_indexer_search_for_applications`, `api_indexer_lookup_application_box`, `api_indexer_lookup_application_boxes`, `api_indexer_lookup_asset_by_id`, `api_indexer_lookup_asset_balances`, `api_indexer_lookup_asset_transactions`, `api_indexer_search_for_assets`, `api_indexer_lookup_transaction_by_id`, `api_indexer_lookup_account_transactions`, `api_indexer_search_for_transactions`
|
|
43
49
|
- **NFDomains** (6) — `api_nfd_get_nfd`, `api_nfd_get_nfds_for_addresses`, `api_nfd_get_nfd_activity`, `api_nfd_get_nfd_analytics`, `api_nfd_browse_nfds`, `api_nfd_search_nfds`
|
|
44
50
|
- **Tinyman AMM** (9) — `api_tinyman_get_pool`, `api_tinyman_get_pool_analytics`, `api_tinyman_get_pool_creation_quote`, `api_tinyman_get_liquidity_quote`, `api_tinyman_get_remove_liquidity_quote`, `api_tinyman_get_swap_quote`, `api_tinyman_get_asset_optin_quote`, `api_tinyman_get_validator_optin_quote`, `api_tinyman_get_validator_optout_quote`
|
|
45
|
-
- **
|
|
51
|
+
- **Haystack Router** (3) — `api_haystack_get_swap_quote`, `api_haystack_execute_swap`, `api_haystack_needs_optin`
|
|
52
|
+
- **Pera Asset Verification** (3) — `api_pera_asset_verification_status`, `api_pera_verified_asset_details`, `api_pera_verified_asset_search`
|
|
53
|
+
- **Alpha Arcade** (15) — Read: `alpha_get_live_markets`, `alpha_get_reward_markets`, `alpha_get_market`, `alpha_get_orderbook`, `alpha_get_open_orders`, `alpha_get_positions`. Trade: `alpha_create_limit_order`, `alpha_create_market_order`, `alpha_cancel_order`, `alpha_amend_order`, `alpha_propose_match`, `alpha_split_shares`, `alpha_merge_shares`, `alpha_claim`
|
|
54
|
+
- **ARC-26 URI** (1) — `generate_algorand_qrcode`
|
|
46
55
|
- **Knowledge** (1) — `get_knowledge_doc` (categories: `arcs`, `sdks`, `algokit`, `algokit-utils`, `tealscript`, `puya`, `liquid-auth`, `python`, `developers`, `clis`, `nodes`, `details`)
|
|
47
56
|
|
|
57
|
+
## QR Code Display (ARC-26 URI)
|
|
58
|
+
|
|
59
|
+
When generating QR codes with `generate_algorand_qrcode`, the tool returns:
|
|
60
|
+
- UTF-8 text QR code (terminal-friendly)
|
|
61
|
+
- PNG image as base64 (web-friendly)
|
|
62
|
+
- URI string
|
|
63
|
+
|
|
64
|
+
**Important:** MCP tool output may not render properly through mcporter → exec pipeline.
|
|
65
|
+
After calling the tool, **extract and paste the QR code directly in your response**:
|
|
66
|
+
|
|
67
|
+
1. Call the tool and capture output
|
|
68
|
+
2. Extract the UTF-8 QR block (Unicode block characters)
|
|
69
|
+
3. Extract the base64 PNG data
|
|
70
|
+
4. Include both in your reply:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
[paste UTF-8 QR here]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
URI: `algorand://ADDRESS?amount=X&asset=Y`
|
|
79
|
+
|
|
80
|
+
This ensures the QR renders correctly in both terminal and web interfaces.
|
|
81
|
+
|
|
48
82
|
## Key things to remember
|
|
49
83
|
|
|
50
84
|
- Always check wallet with `wallet_get_info` before blockchain operations
|
|
@@ -70,6 +104,7 @@ mcporter call algorand-mcp.search_assets name=USDC network=mainnet
|
|
|
70
104
|
- **NEVER use PyTEAL or Beaker** — these are legacy. Use Algorand TypeScript or Algorand Python.
|
|
71
105
|
- **NEVER use AlgoExplorer** — obsolete. Use Allo.info for block/account/transaction data.
|
|
72
106
|
- **NFD (.algo names)**: Always use `depositAccount` field for transactions.
|
|
107
|
+
- **Alpha Arcade prices are microunits**: `yesProb`/`noProb` range 0–1,000,000 (NOT percentages). $0.50 = 500,000. Orders require both ALGO (MBR) and USDC (collateral).
|
|
73
108
|
|
|
74
109
|
## External resources
|
|
75
110
|
|
|
@@ -81,9 +116,12 @@ mcporter call algorand-mcp.search_assets name=USDC network=mainnet
|
|
|
81
116
|
- Testnet Faucet: https://lora.algokit.io/testnet/fund
|
|
82
117
|
- Testnet USDC Faucet: https://faucet.circle.com/
|
|
83
118
|
- Algorand Developer Docs: https://dev.algorand.co/
|
|
84
|
-
- Algorand Developer Docs Github
|
|
85
|
-
- Algorand Developer Examples Github
|
|
86
|
-
- GoPlausible x402-avm Documentation and Example code
|
|
87
|
-
- GoPlausible x402-avm Examples template Projects
|
|
88
|
-
- CAIP-2 Specification
|
|
89
|
-
- Coinbase x402 Protocol
|
|
119
|
+
- Algorand Developer Docs Github: https://github.com/algorandfoundation/devportal
|
|
120
|
+
- Algorand Developer Examples Github: https://github.com/algorandfoundation/devportal-code-examples
|
|
121
|
+
- GoPlausible x402-avm Documentation and Example code: https://github.com/GoPlausible/.github/blob/main/profile/algorand-x402-documentation/README.md
|
|
122
|
+
- GoPlausible x402-avm Examples template Projects: https://github.com/GoPlausible/x402-avm/tree/branch-v2-algorand-publish/examples/
|
|
123
|
+
- CAIP-2 Specification: https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md
|
|
124
|
+
- Coinbase x402 Protocol: https://github.com/coinbase/x402
|
|
125
|
+
- Haystack Router: https://github.com/TxnLab/haystack-router
|
|
126
|
+
- Alpha Arcade: https://alphaarcade.com
|
|
127
|
+
- Alpha Arcade API: https://platform.alphaarcade.com
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-algorand-plugin",
|
|
3
3
|
"name": "Algorand Integration",
|
|
4
4
|
"description": "Algorand blockchain integration with MCP and skills — by GoPlausible",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.8.1",
|
|
6
6
|
"skills": [
|
|
7
7
|
"skills/algorand-development",
|
|
8
8
|
"skills/algorand-typescript",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goplausible/openclaw-algorand-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"index.ts",
|
|
28
28
|
"setup.ts",
|
|
29
29
|
"lib/",
|
|
30
|
+
"scripts/",
|
|
30
31
|
"skills/",
|
|
31
32
|
"memory/",
|
|
32
33
|
"openclaw.plugin.json"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Usage:
|
|
3
|
+
// node backup-keyring.js backup <output-file> — dump address\tmnemonic pairs
|
|
4
|
+
// node backup-keyring.js restore <input-file> — restore mnemonics from backup
|
|
5
|
+
// node backup-keyring.js count — print number of accounts in wallet.db
|
|
6
|
+
// node backup-keyring.js verify — check which accounts have mnemonics in keyring
|
|
7
|
+
|
|
8
|
+
const { Entry } = require('@napi-rs/keyring');
|
|
9
|
+
const initSqlJs = require('sql.js');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
const KEYCHAIN_SERVICE = 'algorand-mcp';
|
|
15
|
+
const WALLET_DB = path.join(os.homedir(), '.algorand-mcp', 'wallet.db');
|
|
16
|
+
|
|
17
|
+
async function getAccounts() {
|
|
18
|
+
if (!fs.existsSync(WALLET_DB)) return [];
|
|
19
|
+
const SQL = await initSqlJs();
|
|
20
|
+
const buf = fs.readFileSync(WALLET_DB);
|
|
21
|
+
const db = new SQL.Database(buf);
|
|
22
|
+
const rows = db.exec('SELECT address FROM accounts');
|
|
23
|
+
db.close();
|
|
24
|
+
if (!rows.length || !rows[0].values.length) return [];
|
|
25
|
+
return rows[0].values.map(r => r[0]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getMnemonic(address) {
|
|
29
|
+
try {
|
|
30
|
+
const entry = new Entry(KEYCHAIN_SERVICE, address);
|
|
31
|
+
const pw = entry.getPassword();
|
|
32
|
+
return pw || null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setMnemonic(address, mnemonic) {
|
|
39
|
+
const entry = new Entry(KEYCHAIN_SERVICE, address);
|
|
40
|
+
entry.setPassword(mnemonic);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const mode = process.argv[2];
|
|
45
|
+
const file = process.argv[3];
|
|
46
|
+
|
|
47
|
+
if (mode === 'count') {
|
|
48
|
+
const accounts = await getAccounts();
|
|
49
|
+
console.log(accounts.length);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (mode === 'verify') {
|
|
54
|
+
const accounts = await getAccounts();
|
|
55
|
+
let ok = 0, missing = 0;
|
|
56
|
+
for (const addr of accounts) {
|
|
57
|
+
if (getMnemonic(addr)) {
|
|
58
|
+
console.log('OK ' + addr);
|
|
59
|
+
ok++;
|
|
60
|
+
} else {
|
|
61
|
+
console.log('MISSING ' + addr);
|
|
62
|
+
missing++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
console.log(`TOTAL=${accounts.length} OK=${ok} MISSING=${missing}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (mode === 'backup') {
|
|
70
|
+
if (!file) { console.error('Usage: backup-keyring.js backup <output-file>'); process.exit(1); }
|
|
71
|
+
const accounts = await getAccounts();
|
|
72
|
+
if (accounts.length === 0) {
|
|
73
|
+
console.log('NO_ACCOUNTS');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let backed = 0, failed = 0;
|
|
77
|
+
const lines = [];
|
|
78
|
+
for (const addr of accounts) {
|
|
79
|
+
const mnemonic = getMnemonic(addr);
|
|
80
|
+
if (mnemonic) {
|
|
81
|
+
lines.push(addr + '\t' + mnemonic);
|
|
82
|
+
backed++;
|
|
83
|
+
} else {
|
|
84
|
+
console.error('MISSING ' + addr);
|
|
85
|
+
failed++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (lines.length > 0) {
|
|
89
|
+
fs.writeFileSync(file, lines.join('\n') + '\n', { mode: 0o600 });
|
|
90
|
+
}
|
|
91
|
+
console.log(`BACKED_UP=${backed} FAILED=${failed}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (mode === 'restore') {
|
|
96
|
+
if (!file) { console.error('Usage: backup-keyring.js restore <input-file>'); process.exit(1); }
|
|
97
|
+
if (!fs.existsSync(file)) { console.error('File not found: ' + file); process.exit(1); }
|
|
98
|
+
const content = fs.readFileSync(file, 'utf-8').trim();
|
|
99
|
+
if (!content) { console.log('RESTORED=0'); return; }
|
|
100
|
+
let restored = 0;
|
|
101
|
+
for (const line of content.split('\n')) {
|
|
102
|
+
const [addr, ...rest] = line.split('\t');
|
|
103
|
+
const mnemonic = rest.join('\t');
|
|
104
|
+
if (addr && mnemonic) {
|
|
105
|
+
setMnemonic(addr, mnemonic);
|
|
106
|
+
restored++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
console.log(`RESTORED=${restored}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.error('Usage: backup-keyring.js <backup|restore|count|verify> [file]');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
main().catch(err => { console.error(err.message); process.exit(1); });
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
MODE="${1:---detect}"
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
|
|
7
|
+
NODE_MODULES="$PLUGIN_DIR/node_modules"
|
|
8
|
+
BACKUP_SCRIPT="$SCRIPT_DIR/backup-keyring.js"
|
|
9
|
+
WALLET_DB="$HOME/.algorand-mcp/wallet.db"
|
|
10
|
+
|
|
11
|
+
# ═══════════════════════════════════════════════════════════
|
|
12
|
+
# 1. OS Detection
|
|
13
|
+
# ═══════════════════════════════════════════════════════════
|
|
14
|
+
if [[ "$(uname -s)" != "Linux" ]]; then
|
|
15
|
+
case "$(uname -s)" in
|
|
16
|
+
Darwin) BACKEND="macOS Keychain" ;;
|
|
17
|
+
*) BACKEND="OS Keychain" ;;
|
|
18
|
+
esac
|
|
19
|
+
if [ "$MODE" = "--detect" ]; then
|
|
20
|
+
echo "PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')"
|
|
21
|
+
echo "BACKEND=$BACKEND"
|
|
22
|
+
echo "PERSISTENT=true"
|
|
23
|
+
echo "HEADLESS=false"
|
|
24
|
+
else
|
|
25
|
+
echo ""
|
|
26
|
+
echo " ✅ $BACKEND — persistent by default. No setup needed."
|
|
27
|
+
echo ""
|
|
28
|
+
fi
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# ═══════════════════════════════════════════════════════════
|
|
33
|
+
# 2. Linux Environment Detection
|
|
34
|
+
# ═══════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
PLATFORM="linux"
|
|
37
|
+
KEYRING_DIR="$HOME/.local/share/keyrings"
|
|
38
|
+
|
|
39
|
+
# Display (headless?)
|
|
40
|
+
HAS_DISPLAY=false
|
|
41
|
+
[[ -n "${DISPLAY:-}" || -n "${WAYLAND_DISPLAY:-}" ]] && HAS_DISPLAY=true
|
|
42
|
+
|
|
43
|
+
# D-Bus session
|
|
44
|
+
HAS_DBUS=false
|
|
45
|
+
[[ -n "${DBUS_SESSION_BUS_ADDRESS:-}" ]] && HAS_DBUS=true
|
|
46
|
+
|
|
47
|
+
# GNOME Keyring daemon running
|
|
48
|
+
KEYRING_RUNNING=false
|
|
49
|
+
pgrep -u "$USER" gnome-keyring-daemon >/dev/null 2>&1 && KEYRING_RUNNING=true
|
|
50
|
+
|
|
51
|
+
# Keyring files on disk
|
|
52
|
+
KEYRING_FILES=false
|
|
53
|
+
[[ -d "$KEYRING_DIR" && "$(ls -A "$KEYRING_DIR" 2>/dev/null)" ]] && KEYRING_FILES=true
|
|
54
|
+
|
|
55
|
+
# Wallet DB exists and account count (via Node.js)
|
|
56
|
+
WALLET_DB_EXISTS=false
|
|
57
|
+
WALLET_DB_COUNT=0
|
|
58
|
+
[[ -f "$WALLET_DB" ]] && WALLET_DB_EXISTS=true
|
|
59
|
+
if [[ "$WALLET_DB_EXISTS" == "true" && -f "$BACKUP_SCRIPT" ]]; then
|
|
60
|
+
WALLET_DB_COUNT=$(NODE_PATH="$NODE_MODULES" node "$BACKUP_SCRIPT" count 2>/dev/null || echo "0")
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Package manager
|
|
64
|
+
PKG=""
|
|
65
|
+
if command -v apt >/dev/null 2>&1; then
|
|
66
|
+
PKG="apt"
|
|
67
|
+
elif command -v dnf >/dev/null 2>&1; then
|
|
68
|
+
PKG="dnf"
|
|
69
|
+
elif command -v yum >/dev/null 2>&1; then
|
|
70
|
+
PKG="yum"
|
|
71
|
+
elif command -v pacman >/dev/null 2>&1; then
|
|
72
|
+
PKG="pacman"
|
|
73
|
+
elif command -v apk >/dev/null 2>&1; then
|
|
74
|
+
PKG="apk"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Container detection
|
|
78
|
+
IN_CONTAINER=false
|
|
79
|
+
[[ -f /.dockerenv ]] && IN_CONTAINER=true
|
|
80
|
+
grep -q 'docker\|lxc\|containerd' /proc/1/cgroup 2>/dev/null && IN_CONTAINER=true
|
|
81
|
+
|
|
82
|
+
# Persistence verdict
|
|
83
|
+
HEADLESS=false
|
|
84
|
+
if [[ "$HAS_DBUS" == "true" && "$KEYRING_RUNNING" == "true" && "$KEYRING_FILES" == "true" ]]; then
|
|
85
|
+
PERSISTENT="true"
|
|
86
|
+
BACKEND="GNOME Keyring (persistent)"
|
|
87
|
+
elif [[ "$HAS_DISPLAY" == "true" ]]; then
|
|
88
|
+
PERSISTENT="true"
|
|
89
|
+
BACKEND="Desktop Keyring (persistent)"
|
|
90
|
+
else
|
|
91
|
+
PERSISTENT="false"
|
|
92
|
+
BACKEND="In-memory (volatile)"
|
|
93
|
+
HEADLESS=true
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ═══════════════════════════════════════════════════════════
|
|
97
|
+
# 3. Detect-only mode
|
|
98
|
+
# ═══════════════════════════════════════════════════════════
|
|
99
|
+
if [ "$MODE" = "--detect" ]; then
|
|
100
|
+
echo "PLATFORM=$PLATFORM"
|
|
101
|
+
echo "BACKEND=$BACKEND"
|
|
102
|
+
echo "PERSISTENT=$PERSISTENT"
|
|
103
|
+
echo "HEADLESS=$HEADLESS"
|
|
104
|
+
echo "HAS_DISPLAY=$HAS_DISPLAY"
|
|
105
|
+
echo "HAS_DBUS=$HAS_DBUS"
|
|
106
|
+
echo "KEYRING_RUNNING=$KEYRING_RUNNING"
|
|
107
|
+
echo "KEYRING_FILES=$KEYRING_FILES"
|
|
108
|
+
echo "WALLET_DB_EXISTS=$WALLET_DB_EXISTS"
|
|
109
|
+
echo "WALLET_DB_COUNT=$WALLET_DB_COUNT"
|
|
110
|
+
echo "PKG_MANAGER=$PKG"
|
|
111
|
+
echo "IN_CONTAINER=$IN_CONTAINER"
|
|
112
|
+
exit 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# ═══════════════════════════════════════════════════════════
|
|
116
|
+
# 4. Setup mode
|
|
117
|
+
# ═══════════════════════════════════════════════════════════
|
|
118
|
+
|
|
119
|
+
echo ""
|
|
120
|
+
echo " ── Algorand MCP Keyring Persistence Setup ──"
|
|
121
|
+
echo ""
|
|
122
|
+
|
|
123
|
+
# ─── 4a. Status display ───
|
|
124
|
+
echo " Platform: Linux"
|
|
125
|
+
echo " Display: $( [[ "$HAS_DISPLAY" == "true" ]] && echo "✅ Yes" || echo "❌ No (headless)" )"
|
|
126
|
+
echo " D-Bus session: $( [[ "$HAS_DBUS" == "true" ]] && echo "✅ Active" || echo "❌ Not found" )"
|
|
127
|
+
echo " Keyring daemon: $( [[ "$KEYRING_RUNNING" == "true" ]] && echo "✅ Running" || echo "❌ Not running" )"
|
|
128
|
+
echo " Keyring files: $( [[ "$KEYRING_FILES" == "true" ]] && echo "✅ Found in $KEYRING_DIR" || echo "❌ None (in-memory only)" )"
|
|
129
|
+
echo " Wallet DB: $( [[ "$WALLET_DB_EXISTS" == "true" ]] && echo "✅ $WALLET_DB ($WALLET_DB_COUNT account(s))" || echo "— Not found (fresh install)" )"
|
|
130
|
+
echo " Package manager: ${PKG:-unknown}"
|
|
131
|
+
echo " Container: $( [[ "$IN_CONTAINER" == "true" ]] && echo "Yes" || echo "No" )"
|
|
132
|
+
echo ""
|
|
133
|
+
|
|
134
|
+
# Already persistent with keyring files on disk?
|
|
135
|
+
if [[ "$PERSISTENT" == "true" && "$KEYRING_FILES" == "true" ]]; then
|
|
136
|
+
echo " ✅ Keyring is persistent and will survive reboots."
|
|
137
|
+
echo ""
|
|
138
|
+
exit 0
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# ─── 4b. Backup existing wallet mnemonics (UPDATE scenario only) ───
|
|
142
|
+
BACKUP_FILE=""
|
|
143
|
+
if [[ "$WALLET_DB_COUNT" -gt 0 && "$KEYRING_RUNNING" == "true" && -f "$BACKUP_SCRIPT" ]]; then
|
|
144
|
+
echo " ── Step 1: Backup wallet mnemonics from current keyring ──"
|
|
145
|
+
echo ""
|
|
146
|
+
echo " Found $WALLET_DB_COUNT account(s) in wallet.db."
|
|
147
|
+
echo " Reading mnemonics from current keyring before setup..."
|
|
148
|
+
echo ""
|
|
149
|
+
|
|
150
|
+
BACKUP_FILE=$(mktemp /tmp/algorand-mcp-keyring-backup.XXXXXX)
|
|
151
|
+
chmod 600 "$BACKUP_FILE"
|
|
152
|
+
|
|
153
|
+
BACKUP_RESULT=$(NODE_PATH="$NODE_MODULES" node "$BACKUP_SCRIPT" backup "$BACKUP_FILE" 2>&1)
|
|
154
|
+
echo " $BACKUP_RESULT"
|
|
155
|
+
|
|
156
|
+
# Check if backup actually has content
|
|
157
|
+
if [[ ! -s "$BACKUP_FILE" ]]; then
|
|
158
|
+
rm -f "$BACKUP_FILE"
|
|
159
|
+
BACKUP_FILE=""
|
|
160
|
+
echo " ℹ️ No mnemonics to backup (keyring may be empty after a reboot)."
|
|
161
|
+
fi
|
|
162
|
+
echo ""
|
|
163
|
+
elif [[ "$WALLET_DB_COUNT" -gt 0 && "$KEYRING_RUNNING" != "true" ]]; then
|
|
164
|
+
echo " ── Step 1: Backup ──"
|
|
165
|
+
echo ""
|
|
166
|
+
echo " ⚠️ Keyring daemon not running — cannot backup existing mnemonics."
|
|
167
|
+
echo " If the system was rebooted, in-memory mnemonics are already lost."
|
|
168
|
+
echo ""
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# ─── 4c. Install keyring packages ───
|
|
172
|
+
echo " ── Step 2: Install keyring packages ──"
|
|
173
|
+
echo ""
|
|
174
|
+
|
|
175
|
+
if [[ -z "$PKG" ]]; then
|
|
176
|
+
echo " Could not detect package manager. Install manually:"
|
|
177
|
+
echo " gnome-keyring, libsecret-tools, dbus-user-session (or equivalent)"
|
|
178
|
+
echo ""
|
|
179
|
+
else
|
|
180
|
+
case "$PKG" in
|
|
181
|
+
apt)
|
|
182
|
+
INSTALL_CMD="sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt update && sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt install -y gnome-keyring libsecret-tools dbus-user-session"
|
|
183
|
+
;;
|
|
184
|
+
dnf)
|
|
185
|
+
INSTALL_CMD="sudo dnf install -y gnome-keyring libsecret libsecret-tools"
|
|
186
|
+
;;
|
|
187
|
+
yum)
|
|
188
|
+
INSTALL_CMD="sudo yum install -y gnome-keyring libsecret libsecret-tools"
|
|
189
|
+
;;
|
|
190
|
+
pacman)
|
|
191
|
+
INSTALL_CMD="sudo pacman -Sy --noconfirm gnome-keyring libsecret"
|
|
192
|
+
;;
|
|
193
|
+
apk)
|
|
194
|
+
INSTALL_CMD="sudo apk add gnome-keyring libsecret dbus secret-tool"
|
|
195
|
+
;;
|
|
196
|
+
esac
|
|
197
|
+
|
|
198
|
+
echo " Run:"
|
|
199
|
+
echo " $INSTALL_CMD"
|
|
200
|
+
echo ""
|
|
201
|
+
read -r -p " Install now? [y/N] " REPLY
|
|
202
|
+
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
|
|
203
|
+
echo ""
|
|
204
|
+
eval "$INSTALL_CMD"
|
|
205
|
+
echo ""
|
|
206
|
+
echo " ✅ Packages installed."
|
|
207
|
+
else
|
|
208
|
+
echo " Skipped. Install later with the command above."
|
|
209
|
+
fi
|
|
210
|
+
echo ""
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# ─── 4d. Enable lingering user session ───
|
|
214
|
+
echo " ── Step 3: Enable user session lingering ──"
|
|
215
|
+
echo ""
|
|
216
|
+
echo " This keeps D-Bus and keyring daemon alive after SSH logout."
|
|
217
|
+
echo ""
|
|
218
|
+
|
|
219
|
+
if loginctl show-user "$USER" 2>/dev/null | grep -q "Linger=yes"; then
|
|
220
|
+
echo " ✅ Lingering already enabled for $USER"
|
|
221
|
+
else
|
|
222
|
+
read -r -p " Enable lingering for $USER? [y/N] " REPLY
|
|
223
|
+
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
|
|
224
|
+
loginctl enable-linger "$USER"
|
|
225
|
+
echo " ✅ Lingering enabled."
|
|
226
|
+
else
|
|
227
|
+
echo " Skipped."
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
echo ""
|
|
231
|
+
|
|
232
|
+
# ─── 4e. Start D-Bus and keyring daemon ───
|
|
233
|
+
echo " ── Step 4: Start D-Bus session and keyring daemon ──"
|
|
234
|
+
echo ""
|
|
235
|
+
|
|
236
|
+
# Ensure D-Bus is available
|
|
237
|
+
if [[ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]]; then
|
|
238
|
+
echo " Starting D-Bus session..."
|
|
239
|
+
eval $(dbus-launch --sh-syntax)
|
|
240
|
+
echo " ✅ D-Bus session started."
|
|
241
|
+
else
|
|
242
|
+
echo " ✅ D-Bus session already active."
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Kill stale keyring daemons (leftover --unlock processes, etc.)
|
|
246
|
+
STALE_COUNT=$(pgrep -u "$USER" gnome-keyring-daemon 2>/dev/null | wc -l)
|
|
247
|
+
if [[ "$STALE_COUNT" -gt 1 ]]; then
|
|
248
|
+
echo " Cleaning up $STALE_COUNT stale keyring daemon processes..."
|
|
249
|
+
pkill -9 -u "$USER" gnome-keyring-daemon 2>/dev/null || true
|
|
250
|
+
sleep 2
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Start the daemon via --start (D-Bus activated, stays resident)
|
|
254
|
+
# Note: on headless, the daemon may be D-Bus activated on demand rather than
|
|
255
|
+
# staying resident. This is normal — secret-tool and @napi-rs/keyring will
|
|
256
|
+
# trigger D-Bus activation automatically when they query the Secret Service.
|
|
257
|
+
echo " Starting gnome-keyring-daemon..."
|
|
258
|
+
eval $(gnome-keyring-daemon --start --components=secrets 2>&1 | grep -v '^\*\*') || true
|
|
259
|
+
sleep 1
|
|
260
|
+
|
|
261
|
+
if pgrep -u "$USER" gnome-keyring-daemon >/dev/null 2>&1; then
|
|
262
|
+
echo " ✅ Keyring daemon running."
|
|
263
|
+
else
|
|
264
|
+
# On D-Bus activated systems, the daemon starts on demand — verify by querying
|
|
265
|
+
if command -v secret-tool >/dev/null 2>&1; then
|
|
266
|
+
secret-tool search --all service algorand-mcp 2>/dev/null && echo " ✅ Keyring daemon available (D-Bus activated)." || true
|
|
267
|
+
fi
|
|
268
|
+
echo " ℹ️ Daemon is D-Bus activated (starts on demand when queried)."
|
|
269
|
+
fi
|
|
270
|
+
echo ""
|
|
271
|
+
|
|
272
|
+
# ─── 4f. Create persistent login keyring collection ───
|
|
273
|
+
KEYRING_FILE="$KEYRING_DIR/login.keyring"
|
|
274
|
+
mkdir -p "$KEYRING_DIR"
|
|
275
|
+
|
|
276
|
+
echo " ── Step 5: Create persistent keyring ──"
|
|
277
|
+
echo ""
|
|
278
|
+
|
|
279
|
+
if [[ -f "$KEYRING_FILE" ]]; then
|
|
280
|
+
echo " ✅ Keyring already exists at $KEYRING_FILE"
|
|
281
|
+
else
|
|
282
|
+
# On headless Linux, gnome-keyring-daemon refuses to create a collection
|
|
283
|
+
# via normal prompts (no GUI). We use the D-Bus internal interface
|
|
284
|
+
# CreateWithMasterPassword to create the "login" collection with an
|
|
285
|
+
# empty master password — making it auto-unlocked and persistent on disk.
|
|
286
|
+
echo " Creating login keyring collection via D-Bus..."
|
|
287
|
+
|
|
288
|
+
python3 << 'PYEOF'
|
|
289
|
+
import sys
|
|
290
|
+
try:
|
|
291
|
+
from jeepney import new_method_call, DBusAddress
|
|
292
|
+
from jeepney.io.blocking import open_dbus_connection
|
|
293
|
+
|
|
294
|
+
conn = open_dbus_connection(bus='SESSION')
|
|
295
|
+
|
|
296
|
+
# Open a plain-text session with the Secret Service
|
|
297
|
+
msg = new_method_call(
|
|
298
|
+
DBusAddress('/org/freedesktop/secrets',
|
|
299
|
+
bus_name='org.freedesktop.secrets',
|
|
300
|
+
interface='org.freedesktop.Secret.Service'),
|
|
301
|
+
'OpenSession',
|
|
302
|
+
'sv',
|
|
303
|
+
('plain', ('s', ''))
|
|
304
|
+
)
|
|
305
|
+
reply = conn.send_and_get_reply(msg)
|
|
306
|
+
session_path = reply.body[1]
|
|
307
|
+
|
|
308
|
+
# Create the "login" collection with empty master password
|
|
309
|
+
# Uses the internal gnome-keyring interface (bypasses GUI prompt)
|
|
310
|
+
props = {
|
|
311
|
+
'org.freedesktop.Secret.Collection.Label': ('s', 'login')
|
|
312
|
+
}
|
|
313
|
+
master_password = (session_path, b'', b'', 'text/plain')
|
|
314
|
+
|
|
315
|
+
msg = new_method_call(
|
|
316
|
+
DBusAddress('/org/freedesktop/secrets',
|
|
317
|
+
bus_name='org.gnome.keyring',
|
|
318
|
+
interface='org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface'),
|
|
319
|
+
'CreateWithMasterPassword',
|
|
320
|
+
'a{sv}(oayays)',
|
|
321
|
+
(props, master_password)
|
|
322
|
+
)
|
|
323
|
+
reply = conn.send_and_get_reply(msg)
|
|
324
|
+
print(f" Collection created: {reply.body[0]}")
|
|
325
|
+
conn.close()
|
|
326
|
+
except Exception as e:
|
|
327
|
+
print(f" Error: {e}", file=sys.stderr)
|
|
328
|
+
sys.exit(1)
|
|
329
|
+
PYEOF
|
|
330
|
+
|
|
331
|
+
if [[ $? -eq 0 && -f "$KEYRING_FILE" ]]; then
|
|
332
|
+
echo " ✅ Persistent keyring created at $KEYRING_FILE"
|
|
333
|
+
else
|
|
334
|
+
FOUND_FILE=$(ls "$KEYRING_DIR"/*.keyring 2>/dev/null | head -1 || true)
|
|
335
|
+
if [[ -n "$FOUND_FILE" ]]; then
|
|
336
|
+
echo " ✅ Persistent keyring created at $FOUND_FILE"
|
|
337
|
+
else
|
|
338
|
+
echo " ⚠️ Keyring file not created. Ensure python3 and jeepney are installed."
|
|
339
|
+
echo " Install with: pip3 install jeepney"
|
|
340
|
+
fi
|
|
341
|
+
fi
|
|
342
|
+
fi
|
|
343
|
+
echo ""
|
|
344
|
+
|
|
345
|
+
# ─── 4g. Restore backed-up wallet mnemonics (UPDATE scenario only) ───
|
|
346
|
+
if [[ -n "$BACKUP_FILE" && -f "$BACKUP_FILE" && -s "$BACKUP_FILE" ]]; then
|
|
347
|
+
echo " ── Step 6: Restore wallet mnemonics ──"
|
|
348
|
+
echo ""
|
|
349
|
+
|
|
350
|
+
RESTORE_RESULT=$(NODE_PATH="$NODE_MODULES" node "$BACKUP_SCRIPT" restore "$BACKUP_FILE" 2>&1)
|
|
351
|
+
echo " $RESTORE_RESULT"
|
|
352
|
+
|
|
353
|
+
# Securely delete backup
|
|
354
|
+
shred -u "$BACKUP_FILE" 2>/dev/null || rm -f "$BACKUP_FILE"
|
|
355
|
+
echo " ✅ Temporary backup securely deleted."
|
|
356
|
+
echo ""
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
# ─── 4h. Docker-specific guidance ───
|
|
360
|
+
if [[ "$IN_CONTAINER" == "true" ]]; then
|
|
361
|
+
echo " ── Docker / Container Notes ──"
|
|
362
|
+
echo ""
|
|
363
|
+
echo " Add to your Dockerfile:"
|
|
364
|
+
echo " RUN apt-get update && apt-get install -y \\"
|
|
365
|
+
echo " gnome-keyring libsecret-tools dbus-user-session \\"
|
|
366
|
+
echo " && rm -rf /var/lib/apt/lists/*"
|
|
367
|
+
echo ""
|
|
368
|
+
echo " Entrypoint wrapper (entrypoint.sh):"
|
|
369
|
+
echo ' #!/bin/bash'
|
|
370
|
+
echo ' export $(dbus-launch)'
|
|
371
|
+
echo ' eval $(gnome-keyring-daemon --start --components=secrets)'
|
|
372
|
+
echo ' # Create login collection if not exists (headless — no GUI prompt)'
|
|
373
|
+
echo ' if [ ! -f ~/.local/share/keyrings/login.keyring ]; then'
|
|
374
|
+
echo ' python3 -c "'
|
|
375
|
+
echo ' from jeepney import new_method_call, DBusAddress'
|
|
376
|
+
echo ' from jeepney.io.blocking import open_dbus_connection'
|
|
377
|
+
echo ' conn = open_dbus_connection(bus=\"SESSION\")'
|
|
378
|
+
echo ' r = conn.send_and_get_reply(new_method_call(DBusAddress(\"/org/freedesktop/secrets\",bus_name=\"org.freedesktop.secrets\",interface=\"org.freedesktop.Secret.Service\"),\"OpenSession\",\"sv\",(\"plain\",(\"s\",\"\"))))'
|
|
379
|
+
echo ' s = r.body[1]'
|
|
380
|
+
echo ' conn.send_and_get_reply(new_method_call(DBusAddress(\"/org/freedesktop/secrets\",bus_name=\"org.gnome.keyring\",interface=\"org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface\"),\"CreateWithMasterPassword\",\"a{sv}(oayays)\",({\"org.freedesktop.Secret.Collection.Label\":(\"s\",\"login\")},(s,b\"\",b\"\",\"text/plain\"))))'
|
|
381
|
+
echo ' conn.close()'
|
|
382
|
+
echo ' "'
|
|
383
|
+
echo ' fi'
|
|
384
|
+
echo ' exec "$@"'
|
|
385
|
+
echo ""
|
|
386
|
+
echo " Persist keyring + wallet data (docker-compose.yml):"
|
|
387
|
+
echo " volumes:"
|
|
388
|
+
echo " - keyring-data:/home/user/.local/share/keyrings"
|
|
389
|
+
echo " - wallet-data:/home/user/.algorand-mcp"
|
|
390
|
+
echo ""
|
|
391
|
+
fi
|
|
392
|
+
|
|
393
|
+
# ─── 4i. Final verification ───
|
|
394
|
+
echo " ── Results ──"
|
|
395
|
+
echo ""
|
|
396
|
+
|
|
397
|
+
# Re-check
|
|
398
|
+
VERIFY_RUNNING=false
|
|
399
|
+
pgrep -u "$USER" gnome-keyring-daemon >/dev/null 2>&1 && VERIFY_RUNNING=true
|
|
400
|
+
|
|
401
|
+
VERIFY_FILES=false
|
|
402
|
+
[[ -d "$KEYRING_DIR" && "$(ls -A "$KEYRING_DIR" 2>/dev/null)" ]] && VERIFY_FILES=true
|
|
403
|
+
|
|
404
|
+
if [[ "$VERIFY_RUNNING" == "true" ]]; then
|
|
405
|
+
echo " ✅ Daemon: Running"
|
|
406
|
+
else
|
|
407
|
+
echo " ✅ Daemon: D-Bus activated (starts on demand)"
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
if [[ "$VERIFY_FILES" == "true" ]]; then
|
|
411
|
+
echo " ✅ Persistent: Yes (files at $KEYRING_DIR)"
|
|
412
|
+
else
|
|
413
|
+
echo " ⚠️ Persistent: No keyring files yet"
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
# Verify wallet mnemonics via Node.js
|
|
417
|
+
if [[ -f "$BACKUP_SCRIPT" && "$WALLET_DB_EXISTS" == "true" ]]; then
|
|
418
|
+
VERIFY_RESULT=$(NODE_PATH="$NODE_MODULES" node "$BACKUP_SCRIPT" verify 2>/dev/null | tail -1)
|
|
419
|
+
echo " ✅ Wallets: $VERIFY_RESULT"
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
echo ""
|
|
423
|
+
echo " Behavior after setup:"
|
|
424
|
+
echo " • Keyring stored on disk, auto-unlocked on boot via D-Bus activation"
|
|
425
|
+
echo " • loginctl linger keeps D-Bus session alive between SSH sessions"
|
|
426
|
+
echo " • Wallet keys persist across reboots — no user interaction needed"
|
|
427
|
+
echo " • Keep agent wallet funds minimal — use QR code top-ups as needed"
|
|
428
|
+
echo ""
|
package/setup.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
|
-
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { ALGORAND_MCP, GOPLAUSIBLE_SERVICES } from "./lib/mcp-servers.js";
|
|
4
6
|
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
5
9
|
export interface AlgorandPluginConfig {
|
|
6
10
|
enableX402: boolean;
|
|
7
11
|
}
|
|
@@ -11,34 +15,14 @@ export async function runSetup(
|
|
|
11
15
|
): Promise<AlgorandPluginConfig | null> {
|
|
12
16
|
p.intro("🔷 Algorand Plugin Setup — powered by GoPlausible");
|
|
13
17
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// }
|
|
23
|
-
|
|
24
|
-
// if (mcpAvailable) {
|
|
25
|
-
// p.note(
|
|
26
|
-
// `MCP Server: ${ALGORAND_MCP.name}\n` +
|
|
27
|
-
// `Type: ${ALGORAND_MCP.type} (local)\n` +
|
|
28
|
-
// `Command: ${ALGORAND_MCP.command}\n` +
|
|
29
|
-
// `Path: ${mcpPath}\n` +
|
|
30
|
-
// `Status: ✅ Available`,
|
|
31
|
-
// "Algorand MCP"
|
|
32
|
-
// );
|
|
33
|
-
// } else {
|
|
34
|
-
// p.log.warn(
|
|
35
|
-
// `algorand-mcp binary not found in PATH.\n\n` +
|
|
36
|
-
// `Options:\n` +
|
|
37
|
-
// ` • Run with npx: npx algorand-mcp\n` +
|
|
38
|
-
// ` • Install globally: npm install -g @goplausible/algorand-mcp\n` +
|
|
39
|
-
// ` • Add node_modules/.bin to PATH`
|
|
40
|
-
// );
|
|
41
|
-
// }
|
|
18
|
+
// Step 1: Keyring persistence check & setup
|
|
19
|
+
p.log.step("Checking keyring persistence for wallet storage...");
|
|
20
|
+
try {
|
|
21
|
+
const scriptPath = join(__dirname, "scripts", "setup-keyring.sh");
|
|
22
|
+
execSync(`bash "${scriptPath}" --setup`, { stdio: "inherit" });
|
|
23
|
+
} catch {
|
|
24
|
+
p.log.warn("Keyring setup script failed — you may need to configure manually.");
|
|
25
|
+
}
|
|
42
26
|
|
|
43
27
|
// Step 2: x402 integration
|
|
44
28
|
const enableX402 = await p.confirm({
|
|
@@ -59,7 +43,7 @@ export async function runSetup(
|
|
|
59
43
|
p.note(
|
|
60
44
|
`x402 Micropayments: ${config.enableX402 ? "Enabled" : "Disabled"}\n\n` +
|
|
61
45
|
`MCP Server Setup:\n` +
|
|
62
|
-
` The Algorand MCP server (${ALGORAND_MCP.command}) provides
|
|
46
|
+
` The Algorand MCP server (${ALGORAND_MCP.command}) provides 107 blockchain tools.\n` +
|
|
63
47
|
` x402 micropayment and AP2 mandate verifiable credentials flows are fully supported in Algorand MCP.\n` ,
|
|
64
48
|
);
|
|
65
49
|
|
|
@@ -43,8 +43,8 @@ Key points:
|
|
|
43
43
|
|
|
44
44
|
1. **Check wallet**: `wallet_get_info` with target `network` — verify an account exists and is active
|
|
45
45
|
2. **If no accounts**: Guide user to create one with `wallet_add_account` (sets nickname and spending limits)
|
|
46
|
-
3. **If needs funding**: Generate ARC-26 QR with `
|
|
47
|
-
4. **If needs USDC funding**: Generate ARC-26 QR with `
|
|
46
|
+
3. **If needs funding**: Generate ARC-26 QR with `generate_algorand_qrcode` or direct to testnet faucet: https://lora.algokit.io/testnet/fund
|
|
47
|
+
4. **If needs USDC funding**: Generate ARC-26 QR with `generate_algorand_qrcode` or direct to testnet faucet: https://faucet.circle.com/
|
|
48
48
|
5. **Confirm network**: Always confirm which network (`mainnet`, `testnet`, `localnet`) before transactions
|
|
49
49
|
|
|
50
50
|
## Network Selection
|
|
@@ -164,7 +164,7 @@ For atomic (all-or-nothing) multi-transaction groups:
|
|
|
164
164
|
|
|
165
165
|
**Pera Asset Verification** (3): `api_pera_asset_verification_status`, `api_pera_verified_asset_details`, `api_pera_verified_asset_search` — mainnet asset verification (verified/trusted/suspicious/unverified), detailed asset info with USD value, and search by name/keyword
|
|
166
166
|
|
|
167
|
-
**ARC-26 URI** (1): `
|
|
167
|
+
**ARC-26 URI** (1): `generate_algorand_qrcode`
|
|
168
168
|
|
|
169
169
|
**Knowledge Base** (1): `get_knowledge_doc`
|
|
170
170
|
|
|
@@ -209,6 +209,31 @@ All prices and quantities use **microunits** (1,000,000 = $1.00 or 1 share). Ord
|
|
|
209
209
|
|
|
210
210
|
> For detailed Alpha Arcade workflows (orderbook mechanics, multi-choice markets, split/merge shares, claiming, collateral model), load the `alpha-arcade-interaction` skill.
|
|
211
211
|
|
|
212
|
+
## QR Code Display (ARC-26 URI)
|
|
213
|
+
|
|
214
|
+
When generating QR codes with `generate_algorand_qrcode`, the tool returns:
|
|
215
|
+
- UTF-8 text QR code (terminal-friendly)
|
|
216
|
+
- PNG image as base64 (web-friendly)
|
|
217
|
+
- URI string
|
|
218
|
+
|
|
219
|
+
**Important:** MCP tool output may not render properly through mcporter → exec pipeline.
|
|
220
|
+
After calling the tool, **extract and paste the QR code directly in your response**:
|
|
221
|
+
|
|
222
|
+
1. Call the tool and capture output
|
|
223
|
+
2. Extract the UTF-8 QR block (Unicode block characters)
|
|
224
|
+
3. Extract the base64 PNG data
|
|
225
|
+
4. Include both in your reply:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
[paste UTF-8 QR here]
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+

|
|
232
|
+
|
|
233
|
+
URI: `algorand://ADDRESS?amount=X&asset=Y`
|
|
234
|
+
|
|
235
|
+
This ensures the QR renders correctly in both terminal and web interfaces.
|
|
236
|
+
|
|
212
237
|
## References
|
|
213
238
|
|
|
214
239
|
For detailed tool documentation:
|
|
@@ -782,20 +782,40 @@ Mainnet asset verification via Pera Wallet API. Use to check if assets are legit
|
|
|
782
782
|
|
|
783
783
|
## ARC-26 URI Tools
|
|
784
784
|
|
|
785
|
-
###
|
|
786
|
-
- **Purpose**: Generate an Algorand payment
|
|
785
|
+
### generate_algorand_qrcode
|
|
786
|
+
- **Purpose**: Generate an Algorand top up payment or asset transferURI and QR code per ARC-26 specification
|
|
787
787
|
- **Parameters**:
|
|
788
788
|
```json
|
|
789
789
|
{
|
|
790
790
|
"address": "receiver_address",
|
|
791
791
|
"label": "Payment label",
|
|
792
792
|
"amount": 1000000,
|
|
793
|
-
"asset": 31566704,
|
|
793
|
+
"asset": 31566704,// optional, if asset transfer; omit or set to 0 for ALGO
|
|
794
794
|
"note": "Payment note",
|
|
795
|
-
"xnote": "Exclusive note"
|
|
795
|
+
"xnote": "Exclusive immutable note"
|
|
796
796
|
}
|
|
797
797
|
```
|
|
798
|
-
- **Returns**:
|
|
798
|
+
- **Returns**: UTF-8 text QR code (terminal-friendly), PNG image as base64 (web-friendly), URI string
|
|
799
|
+
|
|
800
|
+
### QR Code Display
|
|
801
|
+
|
|
802
|
+
**Important:** MCP tool output may not render properly through mcporter → exec pipeline.
|
|
803
|
+
After calling the tool, **extract and paste the QR code directly in your response**:
|
|
804
|
+
|
|
805
|
+
1. Call the tool and capture output
|
|
806
|
+
2. Extract the UTF-8 QR block (Unicode block characters)
|
|
807
|
+
3. Extract the base64 PNG data
|
|
808
|
+
4. Include both in your reply:
|
|
809
|
+
|
|
810
|
+
```
|
|
811
|
+
[paste UTF-8 QR here]
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+

|
|
815
|
+
|
|
816
|
+
URI: `algorand://ADDRESS?amount=X&asset=Y`
|
|
817
|
+
|
|
818
|
+
This ensures the QR renders correctly in both terminal and web interfaces.
|
|
799
819
|
|
|
800
820
|
---
|
|
801
821
|
|
|
@@ -23,7 +23,7 @@ wallet_add_account {
|
|
|
23
23
|
|
|
24
24
|
### Step 3: If account needs funding
|
|
25
25
|
```
|
|
26
|
-
|
|
26
|
+
generate_algorand_qrcode {
|
|
27
27
|
"address": "[wallet_address]",
|
|
28
28
|
"amount": 5000000,
|
|
29
29
|
"note": "Fund testnet account"
|
|
@@ -33,7 +33,7 @@ Or direct user to: https://lora.algokit.io/testnet/fund
|
|
|
33
33
|
|
|
34
34
|
### Step 4: If account needs USDC funding
|
|
35
35
|
```
|
|
36
|
-
|
|
36
|
+
generate_algorand_qrcode {
|
|
37
37
|
"address": "[wallet_address]",
|
|
38
38
|
"asset": 10458941, // USDC on testnet
|
|
39
39
|
"amount": 1000000, // 1 USDC with 6 decimals
|
|
@@ -599,7 +599,7 @@ send_raw_transaction {
|
|
|
599
599
|
When balance is insufficient, generate an ARC-26 QR code for easy funding:
|
|
600
600
|
|
|
601
601
|
```
|
|
602
|
-
|
|
602
|
+
generate_algorand_qrcode {
|
|
603
603
|
"address": "[wallet_address]",
|
|
604
604
|
"amount": 5000000,
|
|
605
605
|
"note": "Fund account for transaction"
|