@bolloon/bolloon-agent 0.4.19 → 0.4.20
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.
|
@@ -2083,6 +2083,261 @@ export function registerBuiltinTools(ctx) {
|
|
|
2083
2083
|
}
|
|
2084
2084
|
});
|
|
2085
2085
|
// ============================================================
|
|
2086
|
+
// 数字资源资产化 (Stage 1-4): 注册→运营(发现/匹配)→交易(x402)→清算(信誉)
|
|
2087
|
+
// ============================================================
|
|
2088
|
+
ctx.tools.set('resource_register', {
|
|
2089
|
+
name: 'resource_register',
|
|
2090
|
+
description: '注册一个数字资源 (A 轻版: 内容CID寻址 + 元数据同步网络 registry; 预留 chain=evm 可升级 tokenURI). type: data|art_product|product_image|tx_link; 内容寻址存 content, 定价/授权/交易链接/收款钱包进资源记录, 供网络内智能体 resource_discover/match/purchase 发现并交易.',
|
|
2091
|
+
parameters: {
|
|
2092
|
+
type: 'data | art_product | product_image | tx_link',
|
|
2093
|
+
title: '资源标题',
|
|
2094
|
+
content: '资源内容 (文本/JSON; 图片 base64 dataURL)',
|
|
2095
|
+
wallet: '可选收款钱包地址 (Stage 3 交易用)',
|
|
2096
|
+
price: '可选 {amount,currency:USDC|token,token?,per?} 字符串或JSON',
|
|
2097
|
+
license: '可选授权 (personal/commercial)',
|
|
2098
|
+
txLink: '可选交易链接',
|
|
2099
|
+
chain: '可选 none|evm (预留升级)',
|
|
2100
|
+
tokenUriTemplate: '可选 evm 时 tokenURI 模板, 如 {base}/{id}',
|
|
2101
|
+
},
|
|
2102
|
+
execute: async (args) => {
|
|
2103
|
+
try {
|
|
2104
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
2105
|
+
const db = getCIDDatabase();
|
|
2106
|
+
const cidd = {
|
|
2107
|
+
save: async (t) => (await db.save({ agentId: String(ctx.agentId || ''), type: 'knowledge', content: t })).id,
|
|
2108
|
+
load: async (c) => { const rec = await db.load(c); return rec ? String(rec.content ?? '') : null; },
|
|
2109
|
+
};
|
|
2110
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2111
|
+
const { registerResource } = await import('./resource-store.js');
|
|
2112
|
+
let price;
|
|
2113
|
+
if (args.price) {
|
|
2114
|
+
try {
|
|
2115
|
+
price = typeof args.price === 'string' ? JSON.parse(args.price) : args.price;
|
|
2116
|
+
}
|
|
2117
|
+
catch {
|
|
2118
|
+
price = undefined;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
const { loadOrCreateWallet } = await import('./resource-wallet.js');
|
|
2122
|
+
const nw = await loadOrCreateWallet().catch(() => null);
|
|
2123
|
+
const walletAddr = nw?.address || '';
|
|
2124
|
+
const r = await registerResource({
|
|
2125
|
+
ownerDid: String(ctx.agentId || ''),
|
|
2126
|
+
type: String(args.type || ''),
|
|
2127
|
+
title: String(args.title || ''),
|
|
2128
|
+
content: String(args.content || ''),
|
|
2129
|
+
wallet: args.wallet ? String(args.wallet) : walletAddr || undefined,
|
|
2130
|
+
price,
|
|
2131
|
+
license: args.license ? String(args.license) : undefined,
|
|
2132
|
+
txLink: args.txLink ? String(args.txLink) : undefined,
|
|
2133
|
+
chain: args.chain,
|
|
2134
|
+
tokenUriTemplate: args.tokenUriTemplate ? String(args.tokenUriTemplate) : undefined,
|
|
2135
|
+
}, { cid: cidd, registry: getAgentRegistry() });
|
|
2136
|
+
if (!r.ok)
|
|
2137
|
+
return { success: false, error: r.error };
|
|
2138
|
+
const res = r.resource;
|
|
2139
|
+
return { success: true, output: `✅ 资源已注册 (A 轻版上链): ${res.resourceId}\n ${res.title} · ${res.type} · CID: ${res.contentCid}${res.wallet ? ` · 钱包 ${res.wallet}` : ''}${res.price ? ` · 价格 ${res.price.amount} ${res.price.currency}${res.price.token ? `(${res.price.token})` : ''}` : ''}${res.license ? ` · ${res.license}` : ''}${res.txLink ? ` · 交易:${res.txLink}` : ''}${res.chain === 'evm' ? ` · ${res.chain}` : ''}\n已同步网络 registry: 智能体可用 resource_discover/match 找到, resource_purchase 交易/访问。` };
|
|
2140
|
+
}
|
|
2141
|
+
catch (e) {
|
|
2142
|
+
return { success: false, error: `resource_register 失败: ${String(e).slice(0, 200)}` };
|
|
2143
|
+
}
|
|
2144
|
+
},
|
|
2145
|
+
});
|
|
2146
|
+
ctx.tools.set('resource_discover', {
|
|
2147
|
+
name: 'resource_discover',
|
|
2148
|
+
description: '发现数字资源 (本机 + 网络 registry): 按 type 或 owner 列出 (谁注册了什么, 定价/授权/交易链接), 供分配/交易决策.',
|
|
2149
|
+
parameters: { type: '可选过滤 type', owner: '可选过滤 owner DID' },
|
|
2150
|
+
execute: async (args) => {
|
|
2151
|
+
try {
|
|
2152
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2153
|
+
const { listResources } = await import('./resource-store.js');
|
|
2154
|
+
const list = await listResources({
|
|
2155
|
+
type: args.type ? String(args.type) : undefined,
|
|
2156
|
+
owner: args.owner ? String(args.owner) : undefined,
|
|
2157
|
+
}, { registry: getAgentRegistry() });
|
|
2158
|
+
if (!list.length)
|
|
2159
|
+
return { success: true, output: '暂无资源 (用 resource_register 注册)' };
|
|
2160
|
+
return { success: true, output: list.map((r) => `${r.resourceId} · ${r.title} · ${r.type}${r.price ? ` · ${r.price.amount} ${r.price.currency}${r.price.token ? `(${r.price.token})` : ''}${r.price.per ? `/${r.price.per}` : ''}` : ''}${r.license ? ` · ${r.license}` : ''}${r.txLink ? ` · 🔗${r.txLink}` : ''}${r.wallet ? ` · 钱包 ${String(r.wallet).slice(0, 10)}…` : ''}${r.chain === 'evm' ? ' · evm' : ''}`).join('\n') + '\n(resource_purchase <id> 交易/访问)' };
|
|
2161
|
+
}
|
|
2162
|
+
catch (e) {
|
|
2163
|
+
return { success: false, error: `resource_discover 失败: ${String(e).slice(0, 200)}` };
|
|
2164
|
+
}
|
|
2165
|
+
},
|
|
2166
|
+
});
|
|
2167
|
+
ctx.tools.set('resource_match', {
|
|
2168
|
+
name: 'resource_match',
|
|
2169
|
+
description: '自动分配匹配: 按需求关键词对资源打分 (标题/类型/授权 + 提供者信誉加权), 返回推荐排序. 用于智能体自动分配资源.',
|
|
2170
|
+
parameters: { query: '需求关键词' },
|
|
2171
|
+
execute: async (args) => {
|
|
2172
|
+
try {
|
|
2173
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2174
|
+
const { queryReputation } = await import('./agent-reputation.js');
|
|
2175
|
+
const registry = getAgentRegistry();
|
|
2176
|
+
const { matchResources } = await import('./resource-store.js');
|
|
2177
|
+
const repQuery = async (owner, type) => {
|
|
2178
|
+
try {
|
|
2179
|
+
const q = await queryReputation(owner, type, registry);
|
|
2180
|
+
const e = q.entries?.[0];
|
|
2181
|
+
return { score: e?.reputation?.score ?? 0, tasks: e?.reputation?.tasks ?? 0, success: e?.reputation?.success ?? 0, failed: e?.reputation?.failed ?? 0 };
|
|
2182
|
+
}
|
|
2183
|
+
catch {
|
|
2184
|
+
return { score: 0, tasks: 0, success: 0, failed: 0 };
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
const scored = await matchResources(String(args.query || ''), { registry, repQuery });
|
|
2188
|
+
if (!scored.length)
|
|
2189
|
+
return { success: true, output: '无匹配资源' };
|
|
2190
|
+
return { success: true, output: scored.map((s) => `${s.resource.resourceId} · ${s.resource.title} · ${s.resource.type} · 得分 ${Math.round(s.score * 10) / 10}${s.resource.license ? ` · ${s.resource.license}` : ''}`).join('\n') };
|
|
2191
|
+
}
|
|
2192
|
+
catch (e) {
|
|
2193
|
+
return { success: false, error: `resource_match 失败: ${String(e).slice(0, 200)}` };
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2196
|
+
});
|
|
2197
|
+
ctx.tools.set('resource_purchase', {
|
|
2198
|
+
name: 'resource_purchase',
|
|
2199
|
+
description: '交易/访问资源: 付费资源走 x402 支付后解锁内容 (结算更新提供者信誉), 免费资源直接取回内容.',
|
|
2200
|
+
parameters: { resourceId: '资源 id' },
|
|
2201
|
+
execute: async (args) => {
|
|
2202
|
+
try {
|
|
2203
|
+
const { getCIDDatabase } = await import('../orbitdb/cid-database.js');
|
|
2204
|
+
const db = getCIDDatabase();
|
|
2205
|
+
const cidd = {
|
|
2206
|
+
save: async (t) => (await db.save({ agentId: String(ctx.agentId || ''), type: 'knowledge', content: t })).id,
|
|
2207
|
+
load: async (c) => { const rec = await db.load(c); return rec ? String(rec.content ?? '') : null; },
|
|
2208
|
+
};
|
|
2209
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2210
|
+
const { queryReputation } = await import('./agent-reputation.js');
|
|
2211
|
+
const registry = getAgentRegistry();
|
|
2212
|
+
const { purchaseResource } = await import('./resource-store.js');
|
|
2213
|
+
const pay = async (spec) => {
|
|
2214
|
+
try {
|
|
2215
|
+
const { x402Pay } = await import('./x402/x402Pay.js');
|
|
2216
|
+
const { loadOrCreateWallet } = await import('./resource-wallet.js');
|
|
2217
|
+
const w = await loadOrCreateWallet().catch(() => null);
|
|
2218
|
+
if (!w?.privateKey)
|
|
2219
|
+
return { success: false, error: '需自动生成 x402 钱包 (~/.bolloon/wallet.json) 才能购买付费资源' };
|
|
2220
|
+
const r = await x402Pay({ privateKey: w.privateKey, amount: String(spec.amount), to: spec.recipient, currency: spec.currency === 'USDC' ? 'USDC' : 'ETH', memo: spec.memo });
|
|
2221
|
+
return { success: r.success, txHash: r.txHash, error: r.error };
|
|
2222
|
+
}
|
|
2223
|
+
catch (e) {
|
|
2224
|
+
return { success: false, error: `x402 支付失败: ${String(e?.message || e).slice(0, 120)}` };
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
const repQuery = async (owner, type) => {
|
|
2228
|
+
try {
|
|
2229
|
+
const q = await queryReputation(owner, type, registry);
|
|
2230
|
+
const e = q.entries?.[0];
|
|
2231
|
+
return { score: e?.reputation?.score ?? 0, tasks: e?.reputation?.tasks ?? 0, success: e?.reputation?.success ?? 0, failed: e?.reputation?.failed ?? 0 };
|
|
2232
|
+
}
|
|
2233
|
+
catch {
|
|
2234
|
+
return { score: 0, tasks: 0, success: 0, failed: 0 };
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
const onSettle = (outcome, resource) => {
|
|
2238
|
+
void import('./agent-reputation.js').then((m) => m.recordServiceOutcome(resource.ownerDid, `resource:${resource.type}`, outcome, registry)).catch(() => { });
|
|
2239
|
+
};
|
|
2240
|
+
const r = await purchaseResource(String(args.resourceId || ''), { cid: cidd, pay, repQuery, onSettle });
|
|
2241
|
+
if (!r.ok)
|
|
2242
|
+
return { success: false, error: r.error || (r.needPay ? '需要支付' : '失败') };
|
|
2243
|
+
return { success: true, output: `${r.txHash ? `💰 已支付 (tx ${String(r.txHash).slice(0, 16)}…) · ` : ''}📄 ${r.resource.title} (${r.resource.type})\n${String(r.content || '').slice(0, 3000)}` };
|
|
2244
|
+
}
|
|
2245
|
+
catch (e) {
|
|
2246
|
+
return { success: false, error: `resource_purchase 失败: ${String(e).slice(0, 200)}` };
|
|
2247
|
+
}
|
|
2248
|
+
},
|
|
2249
|
+
});
|
|
2250
|
+
ctx.tools.set('resource_reputation', {
|
|
2251
|
+
name: 'resource_reputation',
|
|
2252
|
+
description: '清算/信誉: 查资源提供者信誉 (成交记录+score), 供自动分配加权决策.',
|
|
2253
|
+
parameters: { owner: 'owner DID (缺省=本机)' },
|
|
2254
|
+
execute: async (args) => {
|
|
2255
|
+
try {
|
|
2256
|
+
const { getAgentRegistry } = await import('./agent-registry.js');
|
|
2257
|
+
const { queryReputation } = await import('./agent-reputation.js');
|
|
2258
|
+
const registry = getAgentRegistry();
|
|
2259
|
+
const owner = String(args.owner || ctx.agentId || '');
|
|
2260
|
+
const q = await queryReputation(owner, undefined, registry);
|
|
2261
|
+
if (!q.entries?.length)
|
|
2262
|
+
return { success: true, output: `${owner.slice(0, 16)}… 暂无信誉记录` };
|
|
2263
|
+
return { success: true, output: q.entries.map((e) => ` ${e.service} · ${e.reputation.score} (${e.reputation.success}/${e.reputation.tasks} 成交)`).join('\n') };
|
|
2264
|
+
}
|
|
2265
|
+
catch (e) {
|
|
2266
|
+
return { success: false, error: `resource_reputation 失败: ${String(e).slice(0, 200)}` };
|
|
2267
|
+
}
|
|
2268
|
+
},
|
|
2269
|
+
});
|
|
2270
|
+
// ── Stage 1-B: EVM 资产合约 (ERC-721, CID 作 tokenURI) 铸造/流转/查询 ──
|
|
2271
|
+
ctx.tools.set('resource_mint', {
|
|
2272
|
+
name: 'resource_mint',
|
|
2273
|
+
description: '把已注册数字资源铸成 ERC-721 token (内容 CID 作 tokenURI, 不可变随 token 流转). 需配置 EVM executor (部署 contracts/ResourceERC721.sol + 设 __bolloonEvmExecutor 或用 ethers/viem).',
|
|
2274
|
+
parameters: { resourceId: '资源 id' },
|
|
2275
|
+
execute: async (args) => {
|
|
2276
|
+
try {
|
|
2277
|
+
const { getResource } = await import('./resource-store.js');
|
|
2278
|
+
const { mintResourceToken } = await import('./resource-token.js');
|
|
2279
|
+
const r = await getResource(String(args.resourceId || ''));
|
|
2280
|
+
if (!r)
|
|
2281
|
+
return { success: false, error: '资源不存在' };
|
|
2282
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2283
|
+
if (!evm)
|
|
2284
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor) 才能铸造. 部署 contracts/ResourceERC721.sol + 配置 ~/.bolloon/evm-config.json / ethers.' };
|
|
2285
|
+
const { walletAddress } = await import('./resource-wallet.js');
|
|
2286
|
+
const naddr = await walletAddress().catch(() => '');
|
|
2287
|
+
const to = r.wallet || naddr;
|
|
2288
|
+
if (!to)
|
|
2289
|
+
return { success: false, error: '无收款钱包 (部署后设 __bolloonEvmExecutor + 资源 wallet)' };
|
|
2290
|
+
const m = await mintResourceToken({ resourceId: r.resourceId, ownerDid: r.ownerDid, contentCid: r.contentCid, wallet: to, title: r.title }, { evm });
|
|
2291
|
+
if (!m.ok)
|
|
2292
|
+
return { success: false, error: m.error || (m.needConfig ? '需配置 EVM' : '铸造失败') };
|
|
2293
|
+
return { success: true, output: `✅ 已铸造 tokenId=${m.tokenId} · tokenURI=ipfs://${r.contentCid} · 资源 ${r.resourceId}\n(resource_transfer <tokenId> <to> 流转 · resource_token <tokenId> 查询)` };
|
|
2294
|
+
}
|
|
2295
|
+
catch (e) {
|
|
2296
|
+
return { success: false, error: `resource_mint 失败: ${String(e).slice(0, 200)}` };
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
});
|
|
2300
|
+
ctx.tools.set('resource_transfer', {
|
|
2301
|
+
name: 'resource_transfer',
|
|
2302
|
+
description: '流转 ERC-721 token 所有权 (safeTransferFrom) 到新地址.',
|
|
2303
|
+
parameters: { tokenId: 'token id', to: '接收 EVM 地址' },
|
|
2304
|
+
execute: async (args) => {
|
|
2305
|
+
try {
|
|
2306
|
+
const { transferResourceToken } = await import('./resource-token.js');
|
|
2307
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2308
|
+
if (!evm)
|
|
2309
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor)' };
|
|
2310
|
+
const r = await transferResourceToken(String(args.tokenId || ''), String(args.to || ''), { evm });
|
|
2311
|
+
if (!r.ok)
|
|
2312
|
+
return { success: false, error: r.error || '流转失败' };
|
|
2313
|
+
return { success: true, output: `✅ 已流转 token ${r.tokenId} → ${args.to}` };
|
|
2314
|
+
}
|
|
2315
|
+
catch (e) {
|
|
2316
|
+
return { success: false, error: `resource_transfer 失败: ${String(e).slice(0, 200)}` };
|
|
2317
|
+
}
|
|
2318
|
+
},
|
|
2319
|
+
});
|
|
2320
|
+
ctx.tools.set('resource_token', {
|
|
2321
|
+
name: 'resource_token',
|
|
2322
|
+
description: '查询 ERC-721 token: owner + tokenURI (内容 CID).',
|
|
2323
|
+
parameters: { tokenId: 'token id' },
|
|
2324
|
+
execute: async (args) => {
|
|
2325
|
+
try {
|
|
2326
|
+
const { queryResourceToken } = await import('./resource-token.js');
|
|
2327
|
+
const evm = globalThis.__bolloonEvmExecutor;
|
|
2328
|
+
if (!evm)
|
|
2329
|
+
return { success: false, error: '需配置 EVM executor (__bolloonEvmExecutor)' };
|
|
2330
|
+
const r = await queryResourceToken(String(args.tokenId || ''), { evm });
|
|
2331
|
+
if (!r.ok)
|
|
2332
|
+
return { success: false, error: r.error || '查询失败' };
|
|
2333
|
+
return { success: true, output: `🔎 token ${args.tokenId}\n owner: ${r.owner || 'n/a'}\n tokenURI: ${r.tokenUri || 'n/a'}` };
|
|
2334
|
+
}
|
|
2335
|
+
catch (e) {
|
|
2336
|
+
return { success: false, error: `resource_token 失败: ${String(e).slice(0, 200)}` };
|
|
2337
|
+
}
|
|
2338
|
+
},
|
|
2339
|
+
});
|
|
2340
|
+
// ============================================================
|
|
2086
2341
|
// MCP 工具 (2026-08-03) — 外部 MCP server 接入 agent 工具系统
|
|
2087
2342
|
// 配置: ~/.mcp.json (mcpServers), 启动时 initializeMcpAdapter 自动握手发现工具
|
|
2088
2343
|
// mcp_list_tools: 列出已发现的 MCP 工具
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// ─── 数字资源资产化 (Stage 1-4) ────────────────────────────────────────────
|
|
2
|
+
// 经济循环: 注册资源 → 运营(发现/自动分配) → 交易(x402 付费) → 清算(信誉).
|
|
3
|
+
// A 轻版: content 内容寻址(CID) + 元数据(可同步网络 registry) + DID 签名身份;
|
|
4
|
+
// 预留 B: chain='evm' + tokenUriTemplate → 元数据可升级为 tokenURI.
|
|
5
|
+
// 四类统一 schema: data | art_product | product_image | tx_link.
|
|
6
|
+
// 复用现有件: cid_database(内容寻址) / agent-registry(跨机发现/定价/信誉) / x402(交易) / reputation(清算).
|
|
7
|
+
function memStore() {
|
|
8
|
+
let m = [];
|
|
9
|
+
return { get: () => m, set: (x) => { m = x; } };
|
|
10
|
+
}
|
|
11
|
+
/** Stage2: 把资源元数据同步成网络 registry 可发现条目 (AgentService 包装) */
|
|
12
|
+
export function serializeForRegistry(r) {
|
|
13
|
+
return {
|
|
14
|
+
agentId: r.ownerDid,
|
|
15
|
+
name: r.title,
|
|
16
|
+
wallet: r.wallet || '0x0',
|
|
17
|
+
service: {
|
|
18
|
+
name: `resource:${r.type}`,
|
|
19
|
+
description: `数字资源 ${r.type} — ${r.title} (CID ${r.contentCid})`,
|
|
20
|
+
price: r.price ? { amount: r.price.amount, currency: r.price.currency, per: r.price.per || 'access' } : { amount: '0', currency: 'USDC', per: 'access' },
|
|
21
|
+
},
|
|
22
|
+
capabilities: [`resource:${r.type}`, r.resourceId],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Stage2: 跨机可见 — 注册资源时同步到网络 registry (尽力而为) */
|
|
26
|
+
export async function syncResourceToRegistry(r, deps) {
|
|
27
|
+
if (!deps.registry)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
await deps.registry.register(serializeForRegistry(r));
|
|
31
|
+
}
|
|
32
|
+
catch { /* 同步失败不致命 */ }
|
|
33
|
+
}
|
|
34
|
+
export async function registerResource(opts, deps = {}) {
|
|
35
|
+
if (!opts.ownerDid || !opts.type || !opts.title || !opts.content)
|
|
36
|
+
return { ok: false, error: 'ownerDid/type/title/content 必填' };
|
|
37
|
+
const VALID = ['data', 'art_product', 'product_image', 'tx_link'];
|
|
38
|
+
if (!VALID.includes(opts.type))
|
|
39
|
+
return { ok: false, error: `type 必须是 ${VALID.join('|')}` };
|
|
40
|
+
try {
|
|
41
|
+
const cid = deps.cid;
|
|
42
|
+
if (!cid)
|
|
43
|
+
return { ok: false, error: '缺 cid 依赖 (内容寻址不可用)' };
|
|
44
|
+
const contentCid = await cid.save(opts.content);
|
|
45
|
+
const now = deps.now ? deps.now() : new Date().toISOString();
|
|
46
|
+
const rid = deps.rid ? deps.rid() : `res_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
47
|
+
const resource = {
|
|
48
|
+
resourceId: rid, ownerDid: opts.ownerDid, type: opts.type, title: opts.title, contentCid,
|
|
49
|
+
wallet: opts.wallet, price: opts.price, license: opts.license, txLink: opts.txLink, meta: opts.meta,
|
|
50
|
+
chain: opts.chain || 'none', tokenUriTemplate: opts.chain === 'evm' ? opts.tokenUriTemplate : undefined,
|
|
51
|
+
createdAt: now, updatedAt: now,
|
|
52
|
+
};
|
|
53
|
+
(deps.store || memStore()).set([...(deps.store || memStore()).get(), resource]);
|
|
54
|
+
deps.onRegister?.(resource);
|
|
55
|
+
await syncResourceToRegistry(resource, deps); // Stage2 跨机可见
|
|
56
|
+
return { ok: true, resource };
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
return { ok: false, error: `注册失败: ${String(e?.message || e).slice(0, 140)}` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Stage2 运营: 按 type/owner 过滤 (本机索引 + 网络 registry 合并) */
|
|
63
|
+
export async function listResources(filter = {}, deps = {}) {
|
|
64
|
+
const local = (deps.store || memStore()).get()
|
|
65
|
+
.filter((r) => (!filter.type || r.type === filter.type) && (!filter.owner || r.ownerDid === filter.owner));
|
|
66
|
+
// 合并网络 registry (跨机资源), 按 resourceId 去重
|
|
67
|
+
if (deps.registry) {
|
|
68
|
+
try {
|
|
69
|
+
const remote = await deps.registry.discover(filter.type ? `resource:${filter.type}` : 'resource:');
|
|
70
|
+
const known = new Set(local.map((r) => r.resourceId));
|
|
71
|
+
for (const r of remote) {
|
|
72
|
+
const rid = r?.capabilities?.find((c) => String(c).startsWith('res_'));
|
|
73
|
+
if (!rid || known.has(rid))
|
|
74
|
+
continue;
|
|
75
|
+
const price = r?.service?.price;
|
|
76
|
+
local.push({
|
|
77
|
+
resourceId: rid, ownerDid: r?.agentId || 'unknown', type: String(r?.service?.name).replace('resource:', '') || 'data',
|
|
78
|
+
title: r?.name || rid, contentCid: '', wallet: r?.wallet, price: price ? { amount: String(price.amount), currency: price.currency || 'USDC', per: price.per } : undefined,
|
|
79
|
+
chain: 'none', meta: { remote: true },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch { /* 网络合并失败忽略 */ }
|
|
84
|
+
}
|
|
85
|
+
return local;
|
|
86
|
+
}
|
|
87
|
+
export async function getResource(resourceId, deps = {}) {
|
|
88
|
+
return (deps.store || memStore()).get().find((r) => r.resourceId === resourceId) ?? null;
|
|
89
|
+
}
|
|
90
|
+
/** 免费/预览访问: 直接按 contentCid 取回内容 (付费资源应走 purchaseResource) */
|
|
91
|
+
export async function accessResource(resourceId, deps = {}) {
|
|
92
|
+
const r = await getResource(resourceId, deps);
|
|
93
|
+
if (!r)
|
|
94
|
+
return { ok: false, error: `资源不存在: ${resourceId}` };
|
|
95
|
+
if (!deps.cid)
|
|
96
|
+
return { ok: false, error: '缺 cid 依赖', resource: r };
|
|
97
|
+
const content = await deps.cid.load(r.contentCid);
|
|
98
|
+
if (content === null)
|
|
99
|
+
return { ok: false, error: '内容拉取失败 (CID 不可达)', resource: r };
|
|
100
|
+
return { ok: true, content, resource: r };
|
|
101
|
+
}
|
|
102
|
+
/** Stage2 自动分配匹配器: 关键词打分 + 信誉加权, 返回排序资源 */
|
|
103
|
+
export async function matchResources(query, deps = {}) {
|
|
104
|
+
const all = await listResources({}, deps);
|
|
105
|
+
const q = String(query || '').trim().toLowerCase();
|
|
106
|
+
const scored = [];
|
|
107
|
+
for (const r of all) {
|
|
108
|
+
let s = 0;
|
|
109
|
+
if (q) {
|
|
110
|
+
if (r.title?.toLowerCase().includes(q))
|
|
111
|
+
s += 3;
|
|
112
|
+
if (r.type.toLowerCase().includes(q))
|
|
113
|
+
s += 2;
|
|
114
|
+
if (r.license?.toLowerCase().includes(q))
|
|
115
|
+
s += 1;
|
|
116
|
+
if (String(r.ownerDid).toLowerCase().includes(q))
|
|
117
|
+
s += 1;
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
s = 1;
|
|
121
|
+
if (deps.repQuery) {
|
|
122
|
+
try {
|
|
123
|
+
const rep = await deps.repQuery(r.ownerDid, r.type);
|
|
124
|
+
s += rep.score * 5;
|
|
125
|
+
}
|
|
126
|
+
catch { /* 无信誉 */ }
|
|
127
|
+
}
|
|
128
|
+
scored.push({ resource: r, score: s });
|
|
129
|
+
}
|
|
130
|
+
return scored.filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
|
|
131
|
+
}
|
|
132
|
+
/** Stage3 交易 + Stage4 清算: 付费档走注入 pay(x402), 免费直接 access; 结果 onSettle(reputation) */
|
|
133
|
+
export async function purchaseResource(resourceId, deps = {}) {
|
|
134
|
+
const r = await getResource(resourceId, deps);
|
|
135
|
+
if (!r)
|
|
136
|
+
return { ok: false, error: `资源不存在: ${resourceId}` };
|
|
137
|
+
const amount = r.price ? Number(r.price.amount) : 0;
|
|
138
|
+
if (amount > 0) {
|
|
139
|
+
if (!deps.pay)
|
|
140
|
+
return { ok: false, needPay: true, error: '付费资源需要注入 pay (x402)', resource: r };
|
|
141
|
+
if (!r.wallet)
|
|
142
|
+
return { ok: false, needPay: true, error: '资源缺收款钱包 (wallet)', resource: r };
|
|
143
|
+
const p = await deps.pay({ recipient: r.wallet, amount: String(amount), currency: r.price.currency, token: r.price.token, memo: `purchase ${r.resourceId}` });
|
|
144
|
+
if (!p.success) {
|
|
145
|
+
deps.onSettle?.('failed', r);
|
|
146
|
+
return { ok: false, needPay: true, error: p.error || '支付失败', resource: r };
|
|
147
|
+
}
|
|
148
|
+
deps.onSettle?.('success', r);
|
|
149
|
+
// 付费后解锁访问 (内容寻址)
|
|
150
|
+
const content = deps.cid ? await deps.cid.load(r.contentCid) : null;
|
|
151
|
+
return { ok: true, content: content ?? undefined, txHash: p.txHash, resource: r };
|
|
152
|
+
}
|
|
153
|
+
// 免费资源直接访问
|
|
154
|
+
const content = deps.cid ? await deps.cid.load(r.contentCid) : null;
|
|
155
|
+
return { ok: true, content: content ?? undefined, resource: r };
|
|
156
|
+
}
|
|
157
|
+
/** Stage4 清算: 查资源提供者信誉 */
|
|
158
|
+
export async function resourceReputation(ownerDid, deps = {}) {
|
|
159
|
+
if (!deps.repQuery)
|
|
160
|
+
return { ok: false, error: '缺 repQuery 依赖' };
|
|
161
|
+
try {
|
|
162
|
+
const rep = await deps.repQuery(ownerDid);
|
|
163
|
+
return { ok: true, reputation: rep };
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
return { ok: false, error: String(e?.message || e).slice(0, 120) };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// ─── 数字资源资产化 Stage 1-B: EVM 资产合约 (ERC-721) 铸造/流转/查询 ─────────
|
|
2
|
+
// 合约: contracts/ResourceERC721.sol (mint(to,id,tokenUri), safeTransferFrom).
|
|
3
|
+
// 内容寻址 CID 作 tokenURI (不可变, 随 token 流转); 铸造/转移/查询全部走注入的
|
|
4
|
+
// EVM executor (默认接 ethers/viem 或节点; 未配置明确提示), 单测用 fake.
|
|
5
|
+
// 复用: cid_database(内容寻址) / agent-registry(注册) / gateway(跨机可见) — Stage1 已有.
|
|
6
|
+
function memStore() {
|
|
7
|
+
let m = [];
|
|
8
|
+
return { get: () => m, set: (x) => { m = x; } };
|
|
9
|
+
}
|
|
10
|
+
/** 从 ~/.bolloon/evm-config.json 或 env 读 EVM 配置 (合约地址/RPC/chainId). */
|
|
11
|
+
export async function loadEvmConfig(src) {
|
|
12
|
+
try {
|
|
13
|
+
if (src)
|
|
14
|
+
return JSON.parse(src);
|
|
15
|
+
const home = globalThis.process?.env?.HOME || '';
|
|
16
|
+
const { readFile } = await import('fs/promises');
|
|
17
|
+
const raw = await readFile(`${home}/.bolloon/evm-config.json`, 'utf-8').catch(() => null);
|
|
18
|
+
if (raw)
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** 铸造: 内容 CID 作 tokenURI 铸 ERC-721, 记 token 账本 */
|
|
27
|
+
export async function mintResourceToken(resource, deps = {}) {
|
|
28
|
+
if (!deps.evm)
|
|
29
|
+
return { ok: false, needConfig: true, error: '需配置 EVM executor (ethers/viem 或注入) 才能铸造' };
|
|
30
|
+
if (!resource.wallet)
|
|
31
|
+
return { ok: false, needConfig: true, error: '资源缺收款钱包地址 (EVM 接收地址)' };
|
|
32
|
+
if (!resource.contentCid)
|
|
33
|
+
return { ok: false, error: '资源缺 contentCid (内容寻址)' };
|
|
34
|
+
try {
|
|
35
|
+
const tokenUri = `ipfs://${resource.contentCid}`; // CID 作 tokenURI (内容寻址, 不可变)
|
|
36
|
+
const r = await deps.evm.mint(resource.wallet, tokenUri, `mint ${resource.resourceId}`);
|
|
37
|
+
const now = deps.now ? deps.now() : new Date().toISOString();
|
|
38
|
+
const record = { tokenId: r.tokenId, resourceId: resource.resourceId, ownerDid: resource.ownerDid, contract: deps.config?.contractAddress || '', mintedAt: now };
|
|
39
|
+
const st = deps.store || memStore();
|
|
40
|
+
st.set([...st.get(), record]);
|
|
41
|
+
return { ok: true, tokenId: r.tokenId, record };
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return { ok: false, error: `铸造失败: ${String(e?.message || e).slice(0, 140)}` };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** 流转: safeTransferFrom 转移所有权 */
|
|
48
|
+
export async function transferResourceToken(tokenId, to, deps = {}) {
|
|
49
|
+
if (!deps.evm)
|
|
50
|
+
return { ok: false, needConfig: true, error: '需配置 EVM executor 才能流转' };
|
|
51
|
+
try {
|
|
52
|
+
const r = await deps.evm.transfer(tokenId, to);
|
|
53
|
+
if (r.error)
|
|
54
|
+
return { ok: false, error: r.error };
|
|
55
|
+
return { ok: true, tokenId };
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
return { ok: false, error: `流转失败: ${String(e?.message || e).slice(0, 140)}` };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** 查询: owner + tokenURI */
|
|
62
|
+
export async function queryResourceToken(tokenId, deps = {}) {
|
|
63
|
+
if (!deps.evm)
|
|
64
|
+
return { ok: false, error: '需配置 EVM executor 才能查询' };
|
|
65
|
+
try {
|
|
66
|
+
const owner = await deps.evm.ownerOf(tokenId);
|
|
67
|
+
const uri = await deps.evm.tokenURI(tokenId);
|
|
68
|
+
return { ok: true, owner: owner ?? undefined, tokenUri: uri ?? undefined };
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return { ok: false, error: `查询失败: ${String(e?.message || e).slice(0, 140)}` };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** 列出本机已铸 token */
|
|
75
|
+
export function listResourceTokens(deps = {}) {
|
|
76
|
+
return (deps.store || memStore()).get();
|
|
77
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// ─── 资源级 x402 钱包自动配置 ─────────────────────────────────────────────
|
|
2
|
+
// 首次调用自动生成一个 EVM 钱包 (viem/accounts, 私钥+地址), 持久化到
|
|
3
|
+
// ~/.bolloon/wallet.json (mode 0600). 之后幂等加载同一钱包.
|
|
4
|
+
// 资源自动绑定到它: 卖家收款地址(resource.wallet) / 铸造接收地址 / 购买付款签名.
|
|
5
|
+
// x402 支付复用该私钥签名; 资金由用户向 address 充值 (自动配置=密钥/绑定, 不代发币).
|
|
6
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
7
|
+
async function defaultRead() {
|
|
8
|
+
try {
|
|
9
|
+
const { readFile } = await import('fs/promises');
|
|
10
|
+
const home = globalThis.process?.env?.HOME || '';
|
|
11
|
+
return await readFile(`${home}/.bolloon/wallet.json`, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function defaultWrite(payload) {
|
|
18
|
+
const { writeFile, mkdir } = await import('fs/promises');
|
|
19
|
+
const home = globalThis.process?.env?.HOME || '';
|
|
20
|
+
await mkdir(`${home}/.bolloon`, { recursive: true });
|
|
21
|
+
await writeFile(`${home}/.bolloon/wallet.json`, payload, { mode: 0o600 });
|
|
22
|
+
}
|
|
23
|
+
/** 加载或创建钱包 (幂等): 有则返回, 无则 viem 生成 + 落盘 0600. */
|
|
24
|
+
export async function loadOrCreateWallet(deps = {}) {
|
|
25
|
+
const read = deps.read ?? defaultRead;
|
|
26
|
+
const write = deps.write ?? defaultWrite;
|
|
27
|
+
const existing = await read();
|
|
28
|
+
if (existing) {
|
|
29
|
+
try {
|
|
30
|
+
const w = JSON.parse(existing);
|
|
31
|
+
if (w && w.privateKey && w.address)
|
|
32
|
+
return { privateKey: String(w.privateKey), address: String(w.address) };
|
|
33
|
+
}
|
|
34
|
+
catch { /* corrupted → regenerate */ }
|
|
35
|
+
}
|
|
36
|
+
const privateKey = generatePrivateKey();
|
|
37
|
+
const account = privateKeyToAccount(privateKey);
|
|
38
|
+
const rec = { privateKey, address: account.address };
|
|
39
|
+
await write(JSON.stringify(rec));
|
|
40
|
+
return rec;
|
|
41
|
+
}
|
|
42
|
+
/** 便捷: 只取地址 (绑定资源/铸造接收). */
|
|
43
|
+
export async function walletAddress(deps = {}) {
|
|
44
|
+
const w = await loadOrCreateWallet(deps);
|
|
45
|
+
return w.address;
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bolloon/bolloon-agent",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
|
|
6
6
|
"main": "dist/cli-entry.js",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"app:bundle": "node scripts/build-app-bundle.cjs",
|
|
45
45
|
"app:bundle:open": "node scripts/build-app-bundle.cjs && open \"release/mac-arm64/Bolloon Agent.app\"",
|
|
46
46
|
"smoke:esm": "node scripts/smoke-esm.mjs",
|
|
47
|
+
"check:token": "node scripts/solc-compile.mjs",
|
|
47
48
|
"prepublishOnly": "npm run build:all && npm run smoke:esm",
|
|
48
49
|
"test": "vitest run",
|
|
49
50
|
"test:watch": "vitest",
|
|
@@ -114,6 +115,7 @@
|
|
|
114
115
|
"fake-indexeddb": "^6.2.5",
|
|
115
116
|
"lefthook": "^2.1.12",
|
|
116
117
|
"playwright": "^1.63.0",
|
|
118
|
+
"solc": "^0.8.20",
|
|
117
119
|
"tsx": "^4.23.13",
|
|
118
120
|
"typescript": "^7.0.2",
|
|
119
121
|
"vite": "^8.2.2",
|