@goplausible/openclaw-algorand-plugin 1.9.4 → 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.
@@ -0,0 +1,75 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { homedir } from "node:os";
4
+
5
+ import { ALGORAND_MCP } from "./mcp-servers.js";
6
+
7
+ export function getMcpBinaryPath(pluginRoot: string): string {
8
+ const pluginBin = join(pluginRoot, "node_modules", ".bin", "algorand-mcp");
9
+ return existsSync(pluginBin) ? pluginBin : "algorand-mcp";
10
+ }
11
+
12
+ export function isMcpBinaryBundled(pluginRoot: string): boolean {
13
+ return existsSync(join(pluginRoot, "node_modules", ".bin", "algorand-mcp"));
14
+ }
15
+
16
+ export function mcporterConfigPath(): string {
17
+ return join(homedir(), ".mcporter", "mcporter.json");
18
+ }
19
+
20
+ type McporterServerEntry = {
21
+ command: string;
22
+ args?: string[];
23
+ env?: Record<string, string>;
24
+ description?: string;
25
+ };
26
+
27
+ type McporterConfig = {
28
+ mcpServers: Record<string, McporterServerEntry>;
29
+ imports: string[];
30
+ };
31
+
32
+ export function isMcporterConfigured(): boolean {
33
+ const cfgPath = mcporterConfigPath();
34
+ if (!existsSync(cfgPath)) return false;
35
+ try {
36
+ const cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
37
+ return Boolean(cfg.mcpServers?.[ALGORAND_MCP.id]);
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ export function upsertMcporterConfig(pluginRoot: string): { success: boolean; message: string } {
44
+ const cfgPath = mcporterConfigPath();
45
+ const cfgDir = dirname(cfgPath);
46
+
47
+ if (!existsSync(cfgDir)) mkdirSync(cfgDir, { recursive: true });
48
+
49
+ let cfg: McporterConfig = { mcpServers: {}, imports: [] };
50
+ if (existsSync(cfgPath)) {
51
+ try {
52
+ const parsed = JSON.parse(readFileSync(cfgPath, "utf-8"));
53
+ cfg = {
54
+ mcpServers: parsed.mcpServers ?? {},
55
+ imports: Array.isArray(parsed.imports) ? parsed.imports : [],
56
+ };
57
+ } catch (err) {
58
+ return { success: false, message: `Failed to parse ${cfgPath}: ${err}` };
59
+ }
60
+ }
61
+
62
+ const entry: McporterServerEntry = {
63
+ command: getMcpBinaryPath(pluginRoot),
64
+ description: "Algorand blockchain MCP (GoPlausible)",
65
+ };
66
+
67
+ const existing = cfg.mcpServers[ALGORAND_MCP.id];
68
+ if (existing?.command === entry.command && existing?.description === entry.description) {
69
+ return { success: true, message: `algorand-mcp already registered in ${cfgPath}` };
70
+ }
71
+
72
+ cfg.mcpServers[ALGORAND_MCP.id] = entry;
73
+ writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
74
+ return { success: true, message: `algorand-mcp registered in ${cfgPath}` };
75
+ }
@@ -0,0 +1,216 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { homedir } from "node:os";
4
+
5
+ import { upsertMcporterConfig } from "./mcporter.js";
6
+
7
+ const PLUGIN_ID = "openclaw-algorand-plugin";
8
+
9
+ export type WorkspaceApi = {
10
+ logger: { info: (m: string) => void; warn: (m: string) => void; error: (m: string) => void };
11
+ runtime?: {
12
+ agent?: {
13
+ resolveAgentWorkspaceDir?: () => string;
14
+ ensureAgentWorkspace?: () => Promise<string> | string;
15
+ };
16
+ };
17
+ config: Record<string, unknown>;
18
+ };
19
+
20
+ export function resolveWorkspaceDir(api: WorkspaceApi): string {
21
+ const rt = api.runtime?.agent;
22
+ if (rt?.resolveAgentWorkspaceDir) {
23
+ try { return rt.resolveAgentWorkspaceDir(); } catch { /* fall through */ }
24
+ }
25
+ const cfg = api.config as { agents?: { defaults?: { workspace?: string } } };
26
+ return cfg.agents?.defaults?.workspace ?? join(homedir(), ".openclaw", "workspace");
27
+ }
28
+
29
+ export function writeMemoryFile(pluginRoot: string, workspacePath: string): { success: boolean; message: string } {
30
+ const sourceFile = join(pluginRoot, "memory", "algorand-plugin.md");
31
+ const memoryDir = join(workspacePath, "memory");
32
+ const targetFile = join(memoryDir, "algorand-plugin.md");
33
+
34
+ if (!existsSync(sourceFile)) {
35
+ return { success: false, message: `Source memory/algorand-plugin.md not found at ${sourceFile}` };
36
+ }
37
+
38
+ if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
39
+
40
+ writeFileSync(targetFile, readFileSync(sourceFile, "utf-8"));
41
+ return { success: true, message: `Plugin memory written to ${targetFile}` };
42
+ }
43
+
44
+ export function ensureWorkspaceMemoryIndex(pluginRoot: string, workspacePath: string): { success: boolean; message: string } {
45
+ const templateFile = join(pluginRoot, "memory", "MEMORY.md");
46
+
47
+ if (!existsSync(templateFile)) {
48
+ return { success: false, message: "Template MEMORY.md not found in plugin" };
49
+ }
50
+
51
+ const templateContent = readFileSync(templateFile, "utf-8");
52
+
53
+ const neverForgetMatch = templateContent.match(/## NEVER FORGET\n([\s\S]*?)(?=\n## (?!NEVER)|$)/);
54
+ if (!neverForgetMatch) {
55
+ return { success: false, message: "No NEVER FORGET section found in template MEMORY.md" };
56
+ }
57
+ const templateNeverForget = neverForgetMatch[1].trimEnd();
58
+
59
+ const memoryMdPath = join(workspacePath, "MEMORY.md");
60
+ const memoryMdLower = join(workspacePath, "memory.md");
61
+
62
+ const existingPath = existsSync(memoryMdPath) ? memoryMdPath
63
+ : existsSync(memoryMdLower) ? memoryMdLower
64
+ : null;
65
+
66
+ if (!existingPath) {
67
+ if (!existsSync(workspacePath)) mkdirSync(workspacePath, { recursive: true });
68
+ writeFileSync(memoryMdPath, templateContent);
69
+ return { success: true, message: `Created ${memoryMdPath} with NEVER FORGET section` };
70
+ }
71
+
72
+ let existing = readFileSync(existingPath, "utf-8");
73
+
74
+ if (!/## NEVER FORGET/i.test(existing)) {
75
+ const firstHeadingEnd = existing.match(/^# .+\n/m);
76
+ if (firstHeadingEnd) {
77
+ const insertPos = (firstHeadingEnd.index ?? 0) + firstHeadingEnd[0].length;
78
+ existing = existing.slice(0, insertPos) + "\n## NEVER FORGET\n" + templateNeverForget + "\n\n" + existing.slice(insertPos);
79
+ } else {
80
+ existing = "# OpenClaw Agent Long-Term Memory\n\n## NEVER FORGET\n" + templateNeverForget + "\n\n" + existing;
81
+ }
82
+ writeFileSync(existingPath, existing);
83
+ return { success: true, message: `Added NEVER FORGET section to ${existingPath}` };
84
+ }
85
+
86
+ const parseSubsections = (text: string): { header: string; content: string }[] => {
87
+ const sections: { header: string; content: string }[] = [];
88
+ const lines = text.split("\n");
89
+ let currentHeader = "";
90
+ let currentLines: string[] = [];
91
+ for (const line of lines) {
92
+ if (line.startsWith("### ")) {
93
+ if (currentHeader) sections.push({ header: currentHeader, content: currentLines.join("\n").trimEnd() });
94
+ currentHeader = line;
95
+ currentLines = [];
96
+ } else if (currentHeader) {
97
+ currentLines.push(line);
98
+ }
99
+ }
100
+ if (currentHeader) sections.push({ header: currentHeader, content: currentLines.join("\n").trimEnd() });
101
+ return sections;
102
+ };
103
+
104
+ const templateSections = parseSubsections(templateNeverForget);
105
+ const nfSectionMatch = existing.match(/(## NEVER FORGET\n)([\s\S]*?)(?=\n## (?!#)|$)/);
106
+ if (!nfSectionMatch) {
107
+ return { success: true, message: `NEVER FORGET section in ${existingPath} is up to date` };
108
+ }
109
+
110
+ let nfContent = nfSectionMatch[2];
111
+ let updated = false;
112
+
113
+ for (const templateSec of templateSections) {
114
+ if (templateSec.header === "### Never Do This") {
115
+ const neverDoRegex = /(### Never Do This\n)([\s\S]*?)(?=\n### |$)/;
116
+ const existingNeverDoMatch = nfContent.match(neverDoRegex);
117
+
118
+ if (!existingNeverDoMatch) {
119
+ nfContent = nfContent.trimEnd() + "\n\n" + templateSec.header + "\n" + templateSec.content + "\n";
120
+ updated = true;
121
+ } else {
122
+ let existingBullets = existingNeverDoMatch[2];
123
+ const templateBullets = templateSec.content.split("\n").filter((l: string) => l.startsWith("* "));
124
+ const existingBulletLines = existingBullets.split("\n").filter((l: string) => l.startsWith("* "));
125
+
126
+ for (const bullet of templateBullets) {
127
+ const fingerprint = bullet.slice(2, 52).trim();
128
+ const existingMatch = existingBulletLines.find((l: string) => l.includes(fingerprint));
129
+ if (existingMatch) {
130
+ if (existingMatch !== bullet) {
131
+ nfContent = nfContent.replace(existingMatch, bullet);
132
+ updated = true;
133
+ }
134
+ } else {
135
+ existingBullets = existingBullets.trimEnd() + "\n" + bullet;
136
+ nfContent = nfContent.replace(existingNeverDoMatch[2], existingBullets);
137
+ updated = true;
138
+ }
139
+ }
140
+ }
141
+ } else {
142
+ const escapedHeader = templateSec.header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
143
+ const sectionRegex = new RegExp("(" + escapedHeader + "\\n)([\\s\\S]*?)(?=\\n### |$)");
144
+ const existingSecMatch = nfContent.match(sectionRegex);
145
+
146
+ if (existingSecMatch) {
147
+ if (existingSecMatch[2].trimEnd() !== templateSec.content) {
148
+ nfContent = nfContent.replace(existingSecMatch[0], templateSec.header + "\n" + templateSec.content);
149
+ updated = true;
150
+ }
151
+ } else {
152
+ const neverDoPos = nfContent.indexOf("### Never Do This");
153
+ if (neverDoPos !== -1) {
154
+ nfContent = nfContent.slice(0, neverDoPos) + templateSec.header + "\n" + templateSec.content + "\n\n" + nfContent.slice(neverDoPos);
155
+ } else {
156
+ nfContent = nfContent.trimEnd() + "\n\n" + templateSec.header + "\n" + templateSec.content + "\n";
157
+ }
158
+ updated = true;
159
+ }
160
+ }
161
+ }
162
+
163
+ if (!updated) return { success: true, message: `NEVER FORGET section in ${existingPath} is up to date` };
164
+
165
+ existing = existing.replace(nfSectionMatch[2], nfContent);
166
+ writeFileSync(existingPath, existing);
167
+ return { success: true, message: `Updated NEVER FORGET subsections in ${existingPath}` };
168
+ }
169
+
170
+ export function runFirstLoadInit(api: WorkspaceApi, pluginRoot: string, workspacePath: string): void {
171
+ const markerPath = join(workspacePath, ".openclaw", `${PLUGIN_ID}.initialized`);
172
+ if (existsSync(markerPath)) return;
173
+
174
+ const markerDir = dirname(markerPath);
175
+ if (!existsSync(markerDir)) mkdirSync(markerDir, { recursive: true });
176
+
177
+ const mem = writeMemoryFile(pluginRoot, workspacePath);
178
+ if (mem.success) api.logger.info(`[algorand-plugin] ${mem.message}`);
179
+ else api.logger.warn(`[algorand-plugin] ${mem.message}`);
180
+
181
+ const memIdx = ensureWorkspaceMemoryIndex(pluginRoot, workspacePath);
182
+ if (memIdx.success) api.logger.info(`[algorand-plugin] ${memIdx.message}`);
183
+ else api.logger.warn(`[algorand-plugin] ${memIdx.message}`);
184
+
185
+ const mcp = upsertMcporterConfig(pluginRoot);
186
+ if (mcp.success) api.logger.info(`[algorand-plugin] ${mcp.message}`);
187
+ else api.logger.warn(`[algorand-plugin] ${mcp.message}`);
188
+
189
+ writeFileSync(markerPath, new Date().toISOString());
190
+ }
191
+
192
+ export function writePluginConfig(pluginConfig: Record<string, unknown>): { success: boolean; error?: string } {
193
+ try {
194
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
195
+ const configDir = dirname(configPath);
196
+ if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
197
+
198
+ let config: Record<string, any> = {};
199
+ if (existsSync(configPath)) {
200
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
201
+ }
202
+
203
+ config.plugins ??= {};
204
+ config.plugins.entries ??= {};
205
+ config.plugins.entries[PLUGIN_ID] ??= {};
206
+ config.plugins.entries[PLUGIN_ID].config = pluginConfig;
207
+
208
+ config.plugins.allow ??= [];
209
+ if (!config.plugins.allow.includes(PLUGIN_ID)) config.plugins.allow.push(PLUGIN_ID);
210
+
211
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
212
+ return { success: true };
213
+ } catch (err) {
214
+ return { success: false, error: String(err) };
215
+ }
216
+ }
package/memory/MEMORY.md CHANGED
@@ -26,6 +26,12 @@
26
26
  * `generate_algorand_qrcode` returns `qr` (UTF-8 text QR), `uri` (algorand:// URI), `link` (shareable hosted QR URL via QRClaw), and `expires_in` (link validity).
27
27
  * **Channel-aware output**: In TUI/Web channels, include UTF-8 QR block + URI + shareable link. In social channels (Telegram, Discord, WhatsApp, Slack, etc.), skip the QR block (too bulky) and show only URI + shareable link.
28
28
 
29
+ ### Transaction ID Delivery
30
+ * ALWAYS present transaction IDs to the user after any successful transaction submission.
31
+ * **Mainnet** explorer link: `https://allo.info/tx/{txId}`
32
+ * **Testnet** explorer link: `https://lora.algokit.io/testnet/transaction/{txId}`
33
+ * This applies to ALL transaction types: payments, transfers, opt-ins, app calls, atomic groups, Haystack swaps, Alpha Arcade trades, and x402 payments.
34
+
29
35
  ### Documentation
30
36
  * Use `get_knowledge_doc` MCP tool for Algorand developer documentation (categories: arcs, sdks, algokit, algokit-utils, tealscript, puya, liquid-auth, python, developers, clis, nodes, details).
31
37
 
@@ -84,6 +84,17 @@ Shareable QR: [link URL]
84
84
  - **URI string** — for wallet deep links
85
85
  - **Shareable link** — renders nicely in-app as a clickable QR image
86
86
 
87
+ ## Post-Transaction: Deliver Transaction ID
88
+
89
+ **ALWAYS** present the transaction ID to the user after any successful transaction. Use the correct explorer link:
90
+
91
+ | Network | Explorer Link Template |
92
+ |---------|----------------------|
93
+ | `mainnet` | `https://allo.info/tx/{txId}` |
94
+ | `testnet` | `https://lora.algokit.io/testnet/transaction/{txId}` |
95
+
96
+ This applies to ALL operations that yield a transaction ID: payments, asset transfers, opt-ins, app calls, atomic groups, Haystack Router swaps, Alpha Arcade trades, and x402 payments.
97
+
87
98
  ## Key things to remember
88
99
 
89
100
  - Always check wallet with `wallet_get_info` before blockchain operations
@@ -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.9.4",
5
+ "version": "2.0.0",
6
6
  "skills": [
7
7
  "skills/algorand-development",
8
8
  "skills/algorand-typescript",
@@ -14,6 +14,15 @@
14
14
  "skills/haystack-router-interaction",
15
15
  "skills/alpha-arcade-interaction"
16
16
  ],
17
+ "activation": {
18
+ "commands": ["algorand-plugin"]
19
+ },
20
+ "commandAliases": [
21
+ {
22
+ "name": "algorand-plugin",
23
+ "description": "Algorand blockchain integration (GoPlausible)"
24
+ }
25
+ ],
17
26
  "configSchema": {
18
27
  "type": "object",
19
28
  "additionalProperties": false,
@@ -21,7 +30,7 @@
21
30
  "enableX402": {
22
31
  "type": "boolean",
23
32
  "default": true,
24
- "description": "Enable x402 micropayments skills"
33
+ "description": "Enable x402 micropayments skills and x402_fetch tool"
25
34
  }
26
35
  }
27
36
  },
@@ -29,5 +38,19 @@
29
38
  "enableX402": {
30
39
  "label": "Enable x402 Micropayments"
31
40
  }
41
+ },
42
+ "setup": {
43
+ "summary": "Configure Algorand blockchain integration",
44
+ "steps": [
45
+ {
46
+ "id": "x402",
47
+ "label": "x402 Micropayments",
48
+ "description": "Enable the x402 HTTP-native payment protocol support (x402_fetch tool + skills).",
49
+ "configKey": "enableX402"
50
+ }
51
+ ],
52
+ "postSetup": {
53
+ "message": "Restart the OpenClaw gateway to apply changes. algorand-mcp is auto-registered in ~/.mcporter/mcporter.json on first load."
54
+ }
32
55
  }
33
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goplausible/openclaw-algorand-plugin",
3
- "version": "1.9.4",
3
+ "version": "2.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -27,16 +27,25 @@
27
27
  "index.ts",
28
28
  "setup.ts",
29
29
  "lib/",
30
- "scripts/",
31
30
  "skills/",
32
31
  "memory/",
33
32
  "openclaw.plugin.json"
34
33
  ],
35
34
  "openclaw": {
36
- "extensions": ["./index.ts"]
35
+ "extensions": ["./index.ts"],
36
+ "compat": {
37
+ "pluginApi": ">=2026.3.14",
38
+ "minGatewayVersion": "2026.3.14"
39
+ }
37
40
  },
38
41
  "dependencies": {
39
42
  "@clack/prompts": "^0.7.0",
40
43
  "@goplausible/algorand-mcp": "latest"
44
+ },
45
+ "peerDependencies": {
46
+ "openclaw": ">=2026.3.14"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^20.0.0"
41
50
  }
42
51
  }
package/setup.ts CHANGED
@@ -1,30 +1,15 @@
1
1
  import * as p from "@clack/prompts";
2
- import { execSync } from "node:child_process";
3
- import { join, dirname } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
2
  import { ALGORAND_MCP, GOPLAUSIBLE_SERVICES } from "./lib/mcp-servers.js";
6
3
 
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
-
9
4
  export interface AlgorandPluginConfig {
10
5
  enableX402: boolean;
11
6
  }
12
7
 
13
8
  export async function runSetup(
14
- existingConfig?: Partial<AlgorandPluginConfig>
9
+ existingConfig?: Partial<AlgorandPluginConfig>,
15
10
  ): Promise<AlgorandPluginConfig | null> {
16
11
  p.intro("🔷 Algorand Plugin Setup — powered by GoPlausible");
17
12
 
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
- }
26
-
27
- // Step 2: x402 integration
28
13
  const enableX402 = await p.confirm({
29
14
  message: "Enable x402 micropayments integration?",
30
15
  initialValue: existingConfig?.enableX402 ?? true,
@@ -35,23 +20,22 @@ export async function runSetup(
35
20
  return null;
36
21
  }
37
22
 
38
- // Summary
39
23
  const config: AlgorandPluginConfig = {
40
24
  enableX402: enableX402 as boolean,
41
25
  };
42
26
 
43
27
  p.note(
44
28
  `x402 Micropayments: ${config.enableX402 ? "Enabled" : "Disabled"}\n\n` +
45
- `MCP Server Setup:\n` +
46
- ` The Algorand MCP server (${ALGORAND_MCP.command}) provides 107 blockchain tools.\n` +
47
- ` x402 micropayment and AP2 mandate verifiable credentials flows are fully supported in Algorand MCP.\n` ,
29
+ `MCP Server:\n` +
30
+ ` ${ALGORAND_MCP.name} (${ALGORAND_MCP.command}) 107 blockchain tools.\n` +
31
+ ` Registered in ~/.mcporter/mcporter.json on first load.\n` +
32
+ ` x402 micropayment and AP2 mandate flows are fully supported.\n`,
48
33
  );
49
34
 
50
35
  p.outro(
51
36
  `🔷 Algorand plugin configured!\n\n` +
52
- ` Next steps:\n` +
53
- ` 3. Restart OpenClaw gateway\n\n` +
54
- ` GoPlausible website: ${GOPLAUSIBLE_SERVICES.website}`
37
+ ` Next step: restart OpenClaw gateway.\n\n` +
38
+ ` Docs: ${GOPLAUSIBLE_SERVICES.website}`,
55
39
  );
56
40
 
57
41
  return config;
@@ -110,7 +110,26 @@ Always check asset's `decimals` field with `api_algod_get_asset_by_id` before co
110
110
  | 3 | `make_*_txn` | Build the transaction |
111
111
  | 4 | `wallet_sign_transaction` | Sign with active wallet account (enforces limits) |
112
112
  | 5 | `send_raw_transaction` | Submit signed transaction to network |
113
- | 6 | Query tools | Verify result on-chain |
113
+ | 6 | **Present txID** | Show transaction ID with explorer link (see below) |
114
+
115
+ ### Post-Transaction: Deliver Transaction ID
116
+
117
+ **ALWAYS** present the transaction ID to the user after any successful transaction submission. Use the correct explorer link based on the network:
118
+
119
+ | Network | Explorer Link Template |
120
+ |---------|----------------------|
121
+ | `mainnet` | `https://allo.info/tx/{txId}` |
122
+ | `testnet` | `https://lora.algokit.io/testnet/transaction/{txId}` |
123
+
124
+ Example output after a testnet transaction:
125
+ > Transaction confirmed! `TXID123...`
126
+ > View on explorer: https://lora.algokit.io/testnet/transaction/TXID123...
127
+
128
+ Example output after a mainnet transaction:
129
+ > Transaction confirmed! `TXID456...`
130
+ > View on explorer: https://allo.info/tx/TXID456...
131
+
132
+ This applies to ALL transaction types: payments, asset transfers, opt-ins, app calls, atomic groups, and any other operation that yields a transaction ID.
114
133
 
115
134
  ### One-Step Asset Opt-In
116
135
 
@@ -128,6 +147,7 @@ When the user provides their own secret key (not using the wallet):
128
147
  | 1 | `make_*_txn` | Build the transaction |
129
148
  | 2 | `sign_transaction` | Sign with provided secret key hex |
130
149
  | 3 | `send_raw_transaction` | Submit signed transaction |
150
+ | 4 | **Present txID** | Show transaction ID with explorer link |
131
151
 
132
152
  ## Atomic Group Transaction Workflow
133
153
 
@@ -139,6 +159,7 @@ For atomic (all-or-nothing) multi-transaction groups:
139
159
  | 2 | `assign_group_id` | Assign group ID to all transactions |
140
160
  | 3 | `wallet_sign_transaction_group` | Sign all transactions in group with wallet |
141
161
  | 4 | `send_raw_transaction` | Submit all signed transactions |
162
+ | 5 | **Present txIDs** | Show all transaction IDs with explorer links |
142
163
 
143
164
  ## Tool Categories
144
165
 
@@ -79,10 +79,21 @@ send_raw_transaction {
79
79
  }
80
80
  ```
81
81
 
82
- ### Step 5: Verify (optional)
82
+ ### Step 5: Present transaction ID to user
83
+
84
+ **ALWAYS** show the transaction ID with the correct explorer link:
85
+
86
+ - **Mainnet**: `https://allo.info/tx/{txId}`
87
+ - **Testnet**: `https://lora.algokit.io/testnet/transaction/{txId}`
88
+
89
+ Example (testnet):
90
+ > Transaction confirmed! `[txID_from_step_4]`
91
+ > View on explorer: https://lora.algokit.io/testnet/transaction/[txID_from_step_4]
92
+
93
+ Optionally verify on-chain:
83
94
  ```
84
95
  api_indexer_lookup_transaction_by_id {
85
- "txId": "[txID_from_step_3]",
96
+ "txId": "[txID_from_step_4]",
86
97
  "network": "testnet"
87
98
  }
88
99
  ```
@@ -100,7 +111,7 @@ wallet_optin_asset {
100
111
  }
101
112
  ```
102
113
 
103
- This creates, signs, and submits the opt-in transaction in a single call.
114
+ This creates, signs, and submits the opt-in transaction in a single call. Present the returned txID with explorer link.
104
115
 
105
116
  ---
106
117
 
@@ -142,6 +153,12 @@ send_raw_transaction {
142
153
  }
143
154
  ```
144
155
 
156
+ ### Step 5: Present transaction ID to user
157
+
158
+ Show the txID with explorer link:
159
+ - **Testnet**: `https://lora.algokit.io/testnet/transaction/{txId}`
160
+ - **Mainnet**: `https://allo.info/tx/{txId}`
161
+
145
162
  ---
146
163
 
147
164
  ## Asset Transfer Workflow (USDC Example)
@@ -204,6 +221,12 @@ send_raw_transaction {
204
221
  }
205
222
  ```
206
223
 
224
+ ### Step 8: Present transaction ID to user
225
+
226
+ Show the txID with explorer link:
227
+ - **Testnet**: `https://lora.algokit.io/testnet/transaction/{txId}`
228
+ - **Mainnet**: `https://allo.info/tx/{txId}`
229
+
207
230
  ---
208
231
 
209
232
  ## Atomic Group Transaction Workflow
@@ -252,6 +275,12 @@ send_raw_transaction {
252
275
 
253
276
  > Atomic groups are all-or-nothing: either all transactions succeed or none do.
254
277
 
278
+ ### Step 5: Present transaction IDs to user
279
+
280
+ Show all txIDs from the group with explorer links:
281
+ - **Testnet**: `https://lora.algokit.io/testnet/transaction/{txId}`
282
+ - **Mainnet**: `https://allo.info/tx/{txId}`
283
+
255
284
  ---
256
285
 
257
286
  ## Create an ASA (Algorand Standard Asset)
@@ -299,6 +328,12 @@ api_algod_get_pending_transaction {
299
328
  ```
300
329
  > The `asset-index` field in the response contains the new ASA ID.
301
330
 
331
+ ### Step 5: Present transaction ID to user
332
+
333
+ Show the txID with explorer link:
334
+ - **Testnet**: `https://lora.algokit.io/testnet/transaction/{txId}`
335
+ - **Mainnet**: `https://allo.info/tx/{txId}`
336
+
302
337
  ---
303
338
 
304
339
  ## Deploy a Smart Contract
@@ -347,6 +382,12 @@ send_raw_transaction {
347
382
  }
348
383
  ```
349
384
 
385
+ ### Step 5: Present transaction ID to user
386
+
387
+ Show the txID with explorer link:
388
+ - **Testnet**: `https://lora.algokit.io/testnet/transaction/{txId}`
389
+ - **Mainnet**: `https://allo.info/tx/{txId}`
390
+
350
391
  ---
351
392
 
352
393
  ## NFD Lookup
@@ -592,6 +633,12 @@ send_raw_transaction {
592
633
  }
593
634
  ```
594
635
 
636
+ ### Step 4: Present transaction ID to user
637
+
638
+ Show the txID with explorer link:
639
+ - **Testnet**: `https://lora.algokit.io/testnet/transaction/{txId}`
640
+ - **Mainnet**: `https://allo.info/tx/{txId}`
641
+
595
642
  ---
596
643
 
597
644
  ## Top-Up QR Code (Insufficient Funds)
@@ -138,6 +138,7 @@ If a trade fails with an "overspend" error, the wallet lacks sufficient ALGO or
138
138
  2. `get_orderbook` — check available liquidity
139
139
  3. `create_market_order` (auto-matches) or `create_limit_order` (rests on book)
140
140
  4. Save the returned `escrowAppId` — you need it to cancel
141
+ 5. **Present txID** — show transaction ID with explorer link (see Post-Transaction below)
141
142
 
142
143
  ### Checking your portfolio
143
144
  1. `get_positions` — see all YES/NO token balances with market titles and asset IDs
@@ -151,15 +152,29 @@ If a trade fails with an "overspend" error, the wallet lacks sufficient ALGO or
151
152
  ### Cancelling an order
152
153
  1. `get_open_orders` — find the `escrowAppId` and `owner` address
153
154
  2. `cancel_order` with `marketAppId`, `escrowAppId`, and `orderOwner`
155
+ 3. **Present txID** — show transaction ID with explorer link
154
156
 
155
157
  ### Claiming from a resolved market
156
158
  1. `get_positions` — find markets with token balances; note the `yesAssetId` or `noAssetId`
157
159
  2. `claim` with `marketAppId` and the winning token's `assetId`
160
+ 3. **Present txID** — show transaction ID with explorer link
158
161
 
159
162
  ### Providing liquidity (split/merge)
160
163
  1. `split_shares` — convert USDC into equal YES + NO tokens
161
164
  2. Place limit orders on both sides of the book for market making
162
165
  3. `merge_shares` — convert matched YES + NO tokens back to USDC
166
+ 4. **Present txIDs** — show transaction IDs with explorer links for each operation
167
+
168
+ ## Post-Transaction: Deliver Transaction ID
169
+
170
+ **ALWAYS** present the transaction ID to the user after any successful trading operation (orders, cancellations, amendments, claims, splits, merges). Use the correct explorer link based on the network:
171
+
172
+ | Network | Explorer Link Template |
173
+ |---------|----------------------|
174
+ | `mainnet` | `https://allo.info/tx/{txId}` |
175
+ | `testnet` | `https://lora.algokit.io/testnet/transaction/{txId}` |
176
+
177
+ This applies to all Alpha Arcade trading tools: `create_market_order`, `create_limit_order`, `cancel_order`, `amend_order`, `propose_match`, `split_shares`, `merge_shares`, and `claim`.
163
178
 
164
179
  ## Common Pitfalls
165
180