@botcord/openclaw-plugin 0.0.4 → 0.0.6
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 +8 -6
- package/index.ts +3 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/skills/botcord/SKILL.md +84 -6
- package/src/client.ts +65 -6
- package/src/inbound.ts +31 -48
- package/src/tools/coin-format.ts +12 -0
- package/src/tools/directory.ts +15 -0
- package/src/tools/payment-transfer.ts +153 -0
- package/src/tools/payment.ts +384 -0
- package/src/tools/rooms.ts +48 -3
- package/src/tools/subscription.ts +74 -5
- package/src/types.ts +4 -0
- package/src/tools/wallet.ts +0 -208
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { BotCordClient } from "../client.js";
|
|
2
|
+
import type { WalletTransaction } from "../types.js";
|
|
3
|
+
import { formatCoinAmount } from "./coin-format.js";
|
|
4
|
+
|
|
5
|
+
type FollowUpDeliveryResult = {
|
|
6
|
+
attempted: true;
|
|
7
|
+
sent: boolean;
|
|
8
|
+
hub_msg_id?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ContactOnlyTransferResult = {
|
|
13
|
+
tx: WalletTransaction;
|
|
14
|
+
transfer_record_message: FollowUpDeliveryResult;
|
|
15
|
+
notifications: {
|
|
16
|
+
payer: FollowUpDeliveryResult;
|
|
17
|
+
payee: FollowUpDeliveryResult;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function extractTransferMetadata(tx: WalletTransaction): Record<string, unknown> | null {
|
|
22
|
+
if (!tx.metadata_json) return null;
|
|
23
|
+
try {
|
|
24
|
+
return typeof tx.metadata_json === "string"
|
|
25
|
+
? JSON.parse(tx.metadata_json)
|
|
26
|
+
: tx.metadata_json;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatOptionalLine(label: string, value: string | null | undefined): string | null {
|
|
33
|
+
return value ? `${label}: ${value}` : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function assertTransferPeerIsContact(client: BotCordClient, toAgentId: string): Promise<void> {
|
|
37
|
+
const contacts = await client.listContacts();
|
|
38
|
+
const isContact = contacts.some((contact) => contact.contact_agent_id === toAgentId);
|
|
39
|
+
if (!isContact) {
|
|
40
|
+
throw new Error("Transfer is only allowed between contacts. Please add this agent as a contact first.");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildTransferRecordMessage(tx: WalletTransaction): string {
|
|
45
|
+
const metadata = extractTransferMetadata(tx);
|
|
46
|
+
return [
|
|
47
|
+
"[BotCord Transfer]",
|
|
48
|
+
`Status: ${tx.status}`,
|
|
49
|
+
`Transaction: ${tx.tx_id}`,
|
|
50
|
+
`Amount: ${formatCoinAmount(tx.amount_minor)}`,
|
|
51
|
+
`Asset: ${tx.asset_code}`,
|
|
52
|
+
formatOptionalLine("From", tx.from_agent_id),
|
|
53
|
+
formatOptionalLine("To", tx.to_agent_id),
|
|
54
|
+
formatOptionalLine("Memo", typeof metadata?.memo === "string" ? metadata.memo : undefined),
|
|
55
|
+
formatOptionalLine("Reference type", tx.reference_type),
|
|
56
|
+
formatOptionalLine("Reference id", tx.reference_id),
|
|
57
|
+
`Created: ${tx.created_at}`,
|
|
58
|
+
].filter(Boolean).join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildTransferNotificationMessage(
|
|
62
|
+
tx: WalletTransaction,
|
|
63
|
+
role: "payer" | "payee",
|
|
64
|
+
): string {
|
|
65
|
+
if (role === "payer") {
|
|
66
|
+
return `[BotCord Notice] Transfer sent: ${formatCoinAmount(tx.amount_minor)} to ${tx.to_agent_id} (tx: ${tx.tx_id})`;
|
|
67
|
+
}
|
|
68
|
+
return `[BotCord Notice] Payment received: ${formatCoinAmount(tx.amount_minor)} from ${tx.from_agent_id} (tx: ${tx.tx_id})`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatFollowUpDeliverySummary(result: ContactOnlyTransferResult): string {
|
|
72
|
+
const lines = [
|
|
73
|
+
`Transfer record message: ${result.transfer_record_message.sent ? "sent" : "failed"}`,
|
|
74
|
+
`Payer notification: ${result.notifications.payer.sent ? "sent" : "failed"}`,
|
|
75
|
+
`Payee notification: ${result.notifications.payee.sent ? "sent" : "failed"}`,
|
|
76
|
+
];
|
|
77
|
+
const failures = [
|
|
78
|
+
result.transfer_record_message.error,
|
|
79
|
+
result.notifications.payer.error,
|
|
80
|
+
result.notifications.payee.error,
|
|
81
|
+
].filter(Boolean);
|
|
82
|
+
if (failures.length > 0) {
|
|
83
|
+
lines.push("Warning: some follow-up messages failed to send.");
|
|
84
|
+
}
|
|
85
|
+
return lines.join("\n");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function sendRecordMessage(
|
|
89
|
+
client: BotCordClient,
|
|
90
|
+
tx: WalletTransaction,
|
|
91
|
+
): Promise<FollowUpDeliveryResult> {
|
|
92
|
+
try {
|
|
93
|
+
const response = await client.sendMessage(tx.to_agent_id || "", buildTransferRecordMessage(tx));
|
|
94
|
+
return { attempted: true, sent: true, hub_msg_id: response.hub_msg_id };
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
return { attempted: true, sent: false, error: err?.message ?? String(err) };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function sendNotification(
|
|
101
|
+
client: BotCordClient,
|
|
102
|
+
to: string,
|
|
103
|
+
tx: WalletTransaction,
|
|
104
|
+
role: "payer" | "payee",
|
|
105
|
+
): Promise<FollowUpDeliveryResult> {
|
|
106
|
+
try {
|
|
107
|
+
const response = await client.sendSystemMessage(to, buildTransferNotificationMessage(tx, role), {
|
|
108
|
+
event: "wallet_transfer_notice",
|
|
109
|
+
role,
|
|
110
|
+
tx_id: tx.tx_id,
|
|
111
|
+
amount_minor: tx.amount_minor,
|
|
112
|
+
asset_code: tx.asset_code,
|
|
113
|
+
from_agent_id: tx.from_agent_id,
|
|
114
|
+
to_agent_id: tx.to_agent_id,
|
|
115
|
+
reference_type: tx.reference_type,
|
|
116
|
+
reference_id: tx.reference_id,
|
|
117
|
+
});
|
|
118
|
+
return { attempted: true, sent: true, hub_msg_id: response.hub_msg_id };
|
|
119
|
+
} catch (err: any) {
|
|
120
|
+
return { attempted: true, sent: false, error: err?.message ?? String(err) };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function executeContactOnlyTransfer(
|
|
125
|
+
client: BotCordClient,
|
|
126
|
+
params: {
|
|
127
|
+
to_agent_id: string;
|
|
128
|
+
amount_minor: string;
|
|
129
|
+
memo?: string;
|
|
130
|
+
reference_type?: string;
|
|
131
|
+
reference_id?: string;
|
|
132
|
+
metadata?: Record<string, unknown>;
|
|
133
|
+
idempotency_key?: string;
|
|
134
|
+
},
|
|
135
|
+
): Promise<ContactOnlyTransferResult> {
|
|
136
|
+
await assertTransferPeerIsContact(client, params.to_agent_id);
|
|
137
|
+
|
|
138
|
+
const tx = await client.createTransfer(params);
|
|
139
|
+
const [recordMessage, payerNotification, payeeNotification] = await Promise.all([
|
|
140
|
+
sendRecordMessage(client, tx),
|
|
141
|
+
sendNotification(client, client.getAgentId(), tx, "payer"),
|
|
142
|
+
sendNotification(client, params.to_agent_id, tx, "payee"),
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
tx,
|
|
147
|
+
transfer_record_message: recordMessage,
|
|
148
|
+
notifications: {
|
|
149
|
+
payer: payerNotification,
|
|
150
|
+
payee: payeeNotification,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* botcord_payment — Unified payment and transaction tool for BotCord coin flows.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
getSingleAccountModeError,
|
|
6
|
+
resolveAccountConfig,
|
|
7
|
+
isAccountConfigured,
|
|
8
|
+
} from "../config.js";
|
|
9
|
+
import { BotCordClient } from "../client.js";
|
|
10
|
+
import { getConfig as getAppConfig } from "../runtime.js";
|
|
11
|
+
import { formatCoinAmount } from "./coin-format.js";
|
|
12
|
+
import { executeContactOnlyTransfer, formatFollowUpDeliverySummary } from "./payment-transfer.js";
|
|
13
|
+
|
|
14
|
+
function sanitizeBalance(summary: any): any {
|
|
15
|
+
return {
|
|
16
|
+
agent_id: summary.agent_id,
|
|
17
|
+
asset_code: summary.asset_code,
|
|
18
|
+
available_balance: formatCoinAmount(summary.available_balance_minor),
|
|
19
|
+
locked_balance: formatCoinAmount(summary.locked_balance_minor),
|
|
20
|
+
total_balance: formatCoinAmount(summary.total_balance_minor),
|
|
21
|
+
updated_at: summary.updated_at,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatBalance(summary: any): string {
|
|
26
|
+
const available = summary.available_balance_minor ?? "0";
|
|
27
|
+
const locked = summary.locked_balance_minor ?? "0";
|
|
28
|
+
const total = summary.total_balance_minor ?? "0";
|
|
29
|
+
return [
|
|
30
|
+
`Asset: ${summary.asset_code}`,
|
|
31
|
+
`Available: ${formatCoinAmount(available)}`,
|
|
32
|
+
`Locked: ${formatCoinAmount(locked)}`,
|
|
33
|
+
`Total: ${formatCoinAmount(total)}`,
|
|
34
|
+
`Updated: ${summary.updated_at}`,
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatRecipient(agent: any): string {
|
|
39
|
+
return [
|
|
40
|
+
`Agent: ${agent.agent_id}`,
|
|
41
|
+
`Name: ${agent.display_name || "(none)"}`,
|
|
42
|
+
`Policy: ${agent.message_policy || "(unknown)"}`,
|
|
43
|
+
`Endpoints: ${Array.isArray(agent.endpoints) ? agent.endpoints.length : 0}`,
|
|
44
|
+
].join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function extractMetadata(tx: any): Record<string, unknown> | null {
|
|
48
|
+
if (!tx?.metadata_json) return null;
|
|
49
|
+
try {
|
|
50
|
+
return typeof tx.metadata_json === "string"
|
|
51
|
+
? JSON.parse(tx.metadata_json)
|
|
52
|
+
: tx.metadata_json;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function sanitizeTransaction(tx: any): any {
|
|
59
|
+
const metadata = extractMetadata(tx);
|
|
60
|
+
return {
|
|
61
|
+
tx_id: tx.tx_id,
|
|
62
|
+
type: tx.type,
|
|
63
|
+
status: tx.status,
|
|
64
|
+
asset_code: tx.asset_code,
|
|
65
|
+
amount: formatCoinAmount(tx.amount_minor),
|
|
66
|
+
fee: formatCoinAmount(tx.fee_minor),
|
|
67
|
+
from_agent_id: tx.from_agent_id,
|
|
68
|
+
to_agent_id: tx.to_agent_id,
|
|
69
|
+
reference_type: tx.reference_type,
|
|
70
|
+
reference_id: tx.reference_id,
|
|
71
|
+
idempotency_key: tx.idempotency_key,
|
|
72
|
+
metadata: metadata ?? undefined,
|
|
73
|
+
created_at: tx.created_at,
|
|
74
|
+
updated_at: tx.updated_at,
|
|
75
|
+
completed_at: tx.completed_at,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function sanitizeTransferResult(transfer: any): any {
|
|
80
|
+
return {
|
|
81
|
+
tx: sanitizeTransaction(transfer.tx),
|
|
82
|
+
transfer_record_message: transfer.transfer_record_message,
|
|
83
|
+
notifications: transfer.notifications,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatTransaction(tx: any): string {
|
|
88
|
+
const lines = [
|
|
89
|
+
`Transaction: ${tx.tx_id}`,
|
|
90
|
+
`Type: ${tx.type}`,
|
|
91
|
+
`Status: ${tx.status}`,
|
|
92
|
+
`Amount: ${formatCoinAmount(tx.amount_minor)}`,
|
|
93
|
+
`Fee: ${formatCoinAmount(tx.fee_minor)}`,
|
|
94
|
+
];
|
|
95
|
+
if (tx.from_agent_id) lines.push(`From: ${tx.from_agent_id}`);
|
|
96
|
+
if (tx.to_agent_id) lines.push(`To: ${tx.to_agent_id}`);
|
|
97
|
+
if (tx.reference_type) lines.push(`Reference type: ${tx.reference_type}`);
|
|
98
|
+
if (tx.reference_id) lines.push(`Reference id: ${tx.reference_id}`);
|
|
99
|
+
const metadata = extractMetadata(tx);
|
|
100
|
+
if (metadata?.memo) lines.push(`Memo: ${String(metadata.memo)}`);
|
|
101
|
+
if (tx.idempotency_key) lines.push(`Idempotency: ${tx.idempotency_key}`);
|
|
102
|
+
lines.push(`Created: ${tx.created_at}`);
|
|
103
|
+
if (tx.completed_at) lines.push(`Completed: ${tx.completed_at}`);
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatTopup(topup: any): string {
|
|
108
|
+
return [
|
|
109
|
+
`Topup: ${topup.topup_id}`,
|
|
110
|
+
`Status: ${topup.status}`,
|
|
111
|
+
`Amount: ${formatCoinAmount(topup.amount_minor)}`,
|
|
112
|
+
`Channel: ${topup.channel}`,
|
|
113
|
+
`Created: ${topup.created_at}`,
|
|
114
|
+
topup.completed_at ? `Completed: ${topup.completed_at}` : null,
|
|
115
|
+
].filter(Boolean).join("\n");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sanitizeTopup(topup: any): any {
|
|
119
|
+
return {
|
|
120
|
+
topup_id: topup.topup_id,
|
|
121
|
+
status: topup.status,
|
|
122
|
+
asset_code: topup.asset_code,
|
|
123
|
+
amount: formatCoinAmount(topup.amount_minor),
|
|
124
|
+
channel: topup.channel,
|
|
125
|
+
idempotency_key: topup.idempotency_key,
|
|
126
|
+
created_at: topup.created_at,
|
|
127
|
+
updated_at: topup.updated_at,
|
|
128
|
+
completed_at: topup.completed_at,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function formatWithdrawal(withdrawal: any): string {
|
|
133
|
+
return [
|
|
134
|
+
`Withdrawal: ${withdrawal.withdrawal_id}`,
|
|
135
|
+
`Status: ${withdrawal.status}`,
|
|
136
|
+
`Amount: ${formatCoinAmount(withdrawal.amount_minor)}`,
|
|
137
|
+
`Fee: ${formatCoinAmount(withdrawal.fee_minor)}`,
|
|
138
|
+
withdrawal.destination_type ? `Destination type: ${withdrawal.destination_type}` : null,
|
|
139
|
+
`Created: ${withdrawal.created_at}`,
|
|
140
|
+
withdrawal.reviewed_at ? `Reviewed: ${withdrawal.reviewed_at}` : null,
|
|
141
|
+
withdrawal.completed_at ? `Completed: ${withdrawal.completed_at}` : null,
|
|
142
|
+
].filter(Boolean).join("\n");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function sanitizeWithdrawal(withdrawal: any): any {
|
|
146
|
+
return {
|
|
147
|
+
withdrawal_id: withdrawal.withdrawal_id,
|
|
148
|
+
status: withdrawal.status,
|
|
149
|
+
asset_code: withdrawal.asset_code,
|
|
150
|
+
amount: formatCoinAmount(withdrawal.amount_minor),
|
|
151
|
+
fee: formatCoinAmount(withdrawal.fee_minor),
|
|
152
|
+
destination_type: withdrawal.destination_type,
|
|
153
|
+
destination: withdrawal.destination,
|
|
154
|
+
idempotency_key: withdrawal.idempotency_key,
|
|
155
|
+
created_at: withdrawal.created_at,
|
|
156
|
+
updated_at: withdrawal.updated_at,
|
|
157
|
+
reviewed_at: withdrawal.reviewed_at,
|
|
158
|
+
completed_at: withdrawal.completed_at,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function formatLedger(data: any): string {
|
|
163
|
+
const entries = data.entries ?? [];
|
|
164
|
+
if (entries.length === 0) return "No payment ledger entries found.";
|
|
165
|
+
|
|
166
|
+
const lines = entries.map((e: any) => {
|
|
167
|
+
const dir = e.direction === "credit" ? "+" : "-";
|
|
168
|
+
return `${e.created_at} | ${dir}${formatCoinAmount(e.amount_minor)} | bal=${formatCoinAmount(e.balance_after_minor)} | tx=${e.tx_id}`;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (data.has_more) {
|
|
172
|
+
lines.push(`\n(More entries available — use cursor: "${data.next_cursor}")`);
|
|
173
|
+
}
|
|
174
|
+
return lines.join("\n");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function sanitizeLedger(data: any): any {
|
|
178
|
+
const entries = (data.entries ?? []).map((entry: any) => ({
|
|
179
|
+
entry_id: entry.entry_id,
|
|
180
|
+
tx_id: entry.tx_id,
|
|
181
|
+
direction: entry.direction,
|
|
182
|
+
amount: formatCoinAmount(entry.amount_minor),
|
|
183
|
+
balance_after: formatCoinAmount(entry.balance_after_minor),
|
|
184
|
+
created_at: entry.created_at,
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
entries,
|
|
189
|
+
next_cursor: data.next_cursor,
|
|
190
|
+
has_more: data.has_more,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function createPaymentTool(opts?: { name?: string; description?: string }) {
|
|
195
|
+
return {
|
|
196
|
+
name: opts?.name || "botcord_payment",
|
|
197
|
+
description:
|
|
198
|
+
opts?.description ||
|
|
199
|
+
"Manage BotCord coin payments and transactions: verify recipients, check balance, view ledger, transfer coins, create topups and withdrawals, cancel withdrawals, and query transaction status.",
|
|
200
|
+
parameters: {
|
|
201
|
+
type: "object" as const,
|
|
202
|
+
properties: {
|
|
203
|
+
action: {
|
|
204
|
+
type: "string" as const,
|
|
205
|
+
enum: [
|
|
206
|
+
"recipient_verify",
|
|
207
|
+
"balance",
|
|
208
|
+
"ledger",
|
|
209
|
+
"transfer",
|
|
210
|
+
"topup",
|
|
211
|
+
"withdraw",
|
|
212
|
+
"cancel_withdrawal",
|
|
213
|
+
"tx_status",
|
|
214
|
+
],
|
|
215
|
+
description: "Payment action to perform",
|
|
216
|
+
},
|
|
217
|
+
agent_id: {
|
|
218
|
+
type: "string" as const,
|
|
219
|
+
description: "Agent ID (ag_...) — for recipient_verify",
|
|
220
|
+
},
|
|
221
|
+
to_agent_id: {
|
|
222
|
+
type: "string" as const,
|
|
223
|
+
description: "Recipient agent ID (ag_...) — for transfer",
|
|
224
|
+
},
|
|
225
|
+
amount_minor: {
|
|
226
|
+
type: "string" as const,
|
|
227
|
+
description: "Amount in minor units (string) — for transfer, topup, withdraw",
|
|
228
|
+
},
|
|
229
|
+
memo: {
|
|
230
|
+
type: "string" as const,
|
|
231
|
+
description: "Optional payment memo — for transfer",
|
|
232
|
+
},
|
|
233
|
+
reference_type: {
|
|
234
|
+
type: "string" as const,
|
|
235
|
+
description: "Optional business reference type — for transfer",
|
|
236
|
+
},
|
|
237
|
+
reference_id: {
|
|
238
|
+
type: "string" as const,
|
|
239
|
+
description: "Optional business reference ID — for transfer",
|
|
240
|
+
},
|
|
241
|
+
metadata: {
|
|
242
|
+
type: "object" as const,
|
|
243
|
+
description: "Optional metadata object — for transfer or topup",
|
|
244
|
+
},
|
|
245
|
+
idempotency_key: {
|
|
246
|
+
type: "string" as const,
|
|
247
|
+
description: "Optional idempotency key — for transfer, topup, withdraw",
|
|
248
|
+
},
|
|
249
|
+
channel: {
|
|
250
|
+
type: "string" as const,
|
|
251
|
+
description: "Topup channel (e.g. 'mock') — for topup",
|
|
252
|
+
},
|
|
253
|
+
destination_type: {
|
|
254
|
+
type: "string" as const,
|
|
255
|
+
description: "Withdrawal destination type — for withdraw",
|
|
256
|
+
},
|
|
257
|
+
destination: {
|
|
258
|
+
type: "object" as const,
|
|
259
|
+
description: "Withdrawal destination details — for withdraw",
|
|
260
|
+
},
|
|
261
|
+
fee_minor: {
|
|
262
|
+
type: "string" as const,
|
|
263
|
+
description: "Optional withdrawal fee in minor units — for withdraw",
|
|
264
|
+
},
|
|
265
|
+
withdrawal_id: {
|
|
266
|
+
type: "string" as const,
|
|
267
|
+
description: "Withdrawal ID — for cancel_withdrawal",
|
|
268
|
+
},
|
|
269
|
+
tx_id: {
|
|
270
|
+
type: "string" as const,
|
|
271
|
+
description: "Transaction ID — for tx_status",
|
|
272
|
+
},
|
|
273
|
+
cursor: {
|
|
274
|
+
type: "string" as const,
|
|
275
|
+
description: "Pagination cursor — for ledger",
|
|
276
|
+
},
|
|
277
|
+
limit: {
|
|
278
|
+
type: "number" as const,
|
|
279
|
+
description: "Max entries to return — for ledger",
|
|
280
|
+
},
|
|
281
|
+
type: {
|
|
282
|
+
type: "string" as const,
|
|
283
|
+
description: "Filter by transaction type — for ledger",
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
required: ["action"],
|
|
287
|
+
},
|
|
288
|
+
execute: async (_toolCallId: any, args: any) => {
|
|
289
|
+
const cfg = getAppConfig();
|
|
290
|
+
if (!cfg) return { error: "No configuration available" };
|
|
291
|
+
const singleAccountError = getSingleAccountModeError(cfg);
|
|
292
|
+
if (singleAccountError) return { error: singleAccountError };
|
|
293
|
+
|
|
294
|
+
const acct = resolveAccountConfig(cfg);
|
|
295
|
+
if (!isAccountConfigured(acct)) {
|
|
296
|
+
return { error: "BotCord is not configured." };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const client = new BotCordClient(acct);
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
switch (args.action) {
|
|
303
|
+
case "recipient_verify": {
|
|
304
|
+
if (!args.agent_id) return { error: "agent_id is required" };
|
|
305
|
+
const agent = await client.resolve(args.agent_id);
|
|
306
|
+
return { result: formatRecipient(agent), data: agent };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case "balance": {
|
|
310
|
+
const summary = await client.getWallet();
|
|
311
|
+
return { result: formatBalance(summary), data: sanitizeBalance(summary) };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
case "ledger": {
|
|
315
|
+
const opts: { cursor?: string; limit?: number; type?: string } = {};
|
|
316
|
+
if (args.cursor) opts.cursor = args.cursor;
|
|
317
|
+
if (args.limit) opts.limit = args.limit;
|
|
318
|
+
if (args.type) opts.type = args.type;
|
|
319
|
+
const ledger = await client.getWalletLedger(opts);
|
|
320
|
+
return { result: formatLedger(ledger), data: sanitizeLedger(ledger) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
case "transfer": {
|
|
324
|
+
if (!args.to_agent_id) return { error: "to_agent_id is required" };
|
|
325
|
+
if (!args.amount_minor) return { error: "amount_minor is required" };
|
|
326
|
+
const transfer = await executeContactOnlyTransfer(client, {
|
|
327
|
+
to_agent_id: args.to_agent_id,
|
|
328
|
+
amount_minor: args.amount_minor,
|
|
329
|
+
memo: args.memo,
|
|
330
|
+
reference_type: args.reference_type,
|
|
331
|
+
reference_id: args.reference_id,
|
|
332
|
+
metadata: args.metadata,
|
|
333
|
+
idempotency_key: args.idempotency_key,
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
result: `${formatTransaction(transfer.tx)}\n${formatFollowUpDeliverySummary(transfer)}`,
|
|
337
|
+
data: sanitizeTransferResult(transfer),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
case "topup": {
|
|
342
|
+
if (!args.amount_minor) return { error: "amount_minor is required" };
|
|
343
|
+
const topup = await client.createTopup({
|
|
344
|
+
amount_minor: args.amount_minor,
|
|
345
|
+
channel: args.channel,
|
|
346
|
+
metadata: args.metadata,
|
|
347
|
+
idempotency_key: args.idempotency_key,
|
|
348
|
+
});
|
|
349
|
+
return { result: formatTopup(topup), data: sanitizeTopup(topup) };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
case "withdraw": {
|
|
353
|
+
if (!args.amount_minor) return { error: "amount_minor is required" };
|
|
354
|
+
const withdrawal = await client.createWithdrawal({
|
|
355
|
+
amount_minor: args.amount_minor,
|
|
356
|
+
fee_minor: args.fee_minor,
|
|
357
|
+
destination_type: args.destination_type,
|
|
358
|
+
destination: args.destination,
|
|
359
|
+
idempotency_key: args.idempotency_key,
|
|
360
|
+
});
|
|
361
|
+
return { result: formatWithdrawal(withdrawal), data: sanitizeWithdrawal(withdrawal) };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
case "cancel_withdrawal": {
|
|
365
|
+
if (!args.withdrawal_id) return { error: "withdrawal_id is required" };
|
|
366
|
+
const withdrawal = await client.cancelWithdrawal(args.withdrawal_id);
|
|
367
|
+
return { result: formatWithdrawal(withdrawal), data: sanitizeWithdrawal(withdrawal) };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
case "tx_status": {
|
|
371
|
+
if (!args.tx_id) return { error: "tx_id is required" };
|
|
372
|
+
const tx = await client.getWalletTransaction(args.tx_id);
|
|
373
|
+
return { result: formatTransaction(tx), data: sanitizeTransaction(tx) };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
default:
|
|
377
|
+
return { error: `Unknown action: ${args.action}` };
|
|
378
|
+
}
|
|
379
|
+
} catch (err: any) {
|
|
380
|
+
return { error: `Payment action failed: ${err.message}` };
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
package/src/tools/rooms.ts
CHANGED
|
@@ -24,7 +24,7 @@ export function createRoomsTool() {
|
|
|
24
24
|
"create", "list", "info", "update", "discover",
|
|
25
25
|
"join", "leave", "dissolve",
|
|
26
26
|
"members", "invite", "remove_member",
|
|
27
|
-
"promote", "transfer", "permissions",
|
|
27
|
+
"promote", "transfer", "permissions", "mute",
|
|
28
28
|
],
|
|
29
29
|
description: "Room action to perform",
|
|
30
30
|
},
|
|
@@ -58,6 +58,27 @@ export function createRoomsTool() {
|
|
|
58
58
|
type: "boolean" as const,
|
|
59
59
|
description: "Whether all members can post — for create, update",
|
|
60
60
|
},
|
|
61
|
+
default_invite: {
|
|
62
|
+
type: "boolean" as const,
|
|
63
|
+
description: "Whether members can invite by default — for create, update",
|
|
64
|
+
},
|
|
65
|
+
max_members: {
|
|
66
|
+
type: "number" as const,
|
|
67
|
+
description: "Maximum room members — for create, update",
|
|
68
|
+
},
|
|
69
|
+
slow_mode_seconds: {
|
|
70
|
+
type: "number" as const,
|
|
71
|
+
description: "Slow mode interval in seconds — for create, update",
|
|
72
|
+
},
|
|
73
|
+
required_subscription_product_id: {
|
|
74
|
+
type: "string" as const,
|
|
75
|
+
description: "Subscription product required to access this room — for create, update",
|
|
76
|
+
},
|
|
77
|
+
member_ids: {
|
|
78
|
+
type: "array" as const,
|
|
79
|
+
items: { type: "string" as const },
|
|
80
|
+
description: "Initial member agent IDs — for create",
|
|
81
|
+
},
|
|
61
82
|
agent_id: {
|
|
62
83
|
type: "string" as const,
|
|
63
84
|
description: "Agent ID — for invite, remove_member, promote, transfer, permissions",
|
|
@@ -75,6 +96,10 @@ export function createRoomsTool() {
|
|
|
75
96
|
type: "boolean" as const,
|
|
76
97
|
description: "Invite permission override — for permissions",
|
|
77
98
|
},
|
|
99
|
+
muted: {
|
|
100
|
+
type: "boolean" as const,
|
|
101
|
+
description: "Mute or unmute the current member in a room — for mute",
|
|
102
|
+
},
|
|
78
103
|
},
|
|
79
104
|
required: ["action"],
|
|
80
105
|
},
|
|
@@ -101,7 +126,12 @@ export function createRoomsTool() {
|
|
|
101
126
|
rule: args.rule,
|
|
102
127
|
visibility: args.visibility || "private",
|
|
103
128
|
join_policy: args.join_policy,
|
|
129
|
+
required_subscription_product_id: args.required_subscription_product_id,
|
|
130
|
+
max_members: args.max_members,
|
|
104
131
|
default_send: args.default_send,
|
|
132
|
+
default_invite: args.default_invite,
|
|
133
|
+
slow_mode_seconds: args.slow_mode_seconds,
|
|
134
|
+
member_ids: args.member_ids,
|
|
105
135
|
});
|
|
106
136
|
|
|
107
137
|
case "list":
|
|
@@ -119,7 +149,11 @@ export function createRoomsTool() {
|
|
|
119
149
|
rule: args.rule,
|
|
120
150
|
visibility: args.visibility,
|
|
121
151
|
join_policy: args.join_policy,
|
|
152
|
+
required_subscription_product_id: args.required_subscription_product_id,
|
|
153
|
+
max_members: args.max_members,
|
|
122
154
|
default_send: args.default_send,
|
|
155
|
+
default_invite: args.default_invite,
|
|
156
|
+
slow_mode_seconds: args.slow_mode_seconds,
|
|
123
157
|
});
|
|
124
158
|
|
|
125
159
|
case "discover":
|
|
@@ -127,7 +161,10 @@ export function createRoomsTool() {
|
|
|
127
161
|
|
|
128
162
|
case "join":
|
|
129
163
|
if (!args.room_id) return { error: "room_id is required" };
|
|
130
|
-
await client.joinRoom(args.room_id
|
|
164
|
+
await client.joinRoom(args.room_id, {
|
|
165
|
+
can_send: args.can_send,
|
|
166
|
+
can_invite: args.can_invite,
|
|
167
|
+
});
|
|
131
168
|
return { ok: true, joined: args.room_id };
|
|
132
169
|
|
|
133
170
|
case "leave":
|
|
@@ -146,7 +183,10 @@ export function createRoomsTool() {
|
|
|
146
183
|
|
|
147
184
|
case "invite":
|
|
148
185
|
if (!args.room_id || !args.agent_id) return { error: "room_id and agent_id are required" };
|
|
149
|
-
await client.inviteToRoom(args.room_id, args.agent_id
|
|
186
|
+
await client.inviteToRoom(args.room_id, args.agent_id, {
|
|
187
|
+
can_send: args.can_send,
|
|
188
|
+
can_invite: args.can_invite,
|
|
189
|
+
});
|
|
150
190
|
return { ok: true, invited: args.agent_id, room: args.room_id };
|
|
151
191
|
|
|
152
192
|
case "remove_member":
|
|
@@ -172,6 +212,11 @@ export function createRoomsTool() {
|
|
|
172
212
|
});
|
|
173
213
|
return { ok: true, agent: args.agent_id, room: args.room_id };
|
|
174
214
|
|
|
215
|
+
case "mute":
|
|
216
|
+
if (!args.room_id) return { error: "room_id is required" };
|
|
217
|
+
await client.muteRoom(args.room_id, args.muted ?? true);
|
|
218
|
+
return { ok: true, room: args.room_id, muted: args.muted ?? true };
|
|
219
|
+
|
|
175
220
|
default:
|
|
176
221
|
return { error: `Unknown action: ${args.action}` };
|
|
177
222
|
}
|