kompass-sdk 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +0 -0
- package/dist/matching.d.ts.map +1 -1
- package/dist/matching.js +8 -0
- package/dist/matching.js.map +1 -1
- package/dist/sources/acp.d.ts +1 -1
- package/dist/sources/acp.d.ts.map +1 -1
- package/dist/sources/acp.js +27 -17
- package/dist/sources/acp.js.map +1 -1
- package/dist/sources/bankr.d.ts +8 -0
- package/dist/sources/bankr.d.ts.map +1 -0
- package/dist/sources/bankr.js +79 -0
- package/dist/sources/bankr.js.map +1 -0
- package/dist/sources/index.d.ts.map +1 -1
- package/dist/sources/index.js +6 -0
- package/dist/sources/index.js.map +1 -1
- package/dist/sources/locus.d.ts +12 -0
- package/dist/sources/locus.d.ts.map +1 -0
- package/dist/sources/locus.js +134 -0
- package/dist/sources/locus.js.map +1 -0
- package/dist/sources/olas.d.ts +12 -0
- package/dist/sources/olas.d.ts.map +1 -0
- package/dist/sources/olas.js +143 -0
- package/dist/sources/olas.js.map +1 -0
- package/dist/sources/openserv.d.ts +8 -0
- package/dist/sources/openserv.d.ts.map +1 -0
- package/dist/sources/openserv.js +76 -0
- package/dist/sources/openserv.js.map +1 -0
- package/dist/sources/types.d.ts +1 -1
- package/dist/sources/types.d.ts.map +1 -1
- package/dist/wallet/bridge.d.ts +1 -0
- package/dist/wallet/bridge.d.ts.map +1 -1
- package/dist/wallet/bridge.js +31 -5
- package/dist/wallet/bridge.js.map +1 -1
- package/dist/wallet/handlers/acp.d.ts.map +1 -1
- package/dist/wallet/handlers/acp.js +59 -3
- package/dist/wallet/handlers/acp.js.map +1 -1
- package/dist/wallet/handlers/bankr.d.ts +13 -0
- package/dist/wallet/handlers/bankr.d.ts.map +1 -0
- package/dist/wallet/handlers/bankr.js +88 -0
- package/dist/wallet/handlers/bankr.js.map +1 -0
- package/dist/wallet/handlers/locus.d.ts +21 -0
- package/dist/wallet/handlers/locus.d.ts.map +1 -0
- package/dist/wallet/handlers/locus.js +220 -0
- package/dist/wallet/handlers/locus.js.map +1 -0
- package/dist/wallet/payment-router.d.ts.map +1 -1
- package/dist/wallet/payment-router.js +12 -0
- package/dist/wallet/payment-router.js.map +1 -1
- package/package.json +1 -1
- package/src/matching.ts +9 -0
- package/src/sources/acp.ts +29 -20
- package/src/sources/bankr.ts +87 -0
- package/src/sources/index.ts +6 -0
- package/src/sources/locus.ts +156 -0
- package/src/sources/olas.ts +171 -0
- package/src/sources/types.ts +4 -1
- package/src/wallet/bridge.ts +33 -6
- package/src/wallet/handlers/acp.ts +69 -3
- package/src/wallet/handlers/bankr.ts +108 -0
- package/src/wallet/handlers/locus.ts +262 -0
- package/src/wallet/payment-router.ts +14 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locus Wrapped API Source Adapter
|
|
3
|
+
* Fetches Locus's live API catalog and indexes 50+ pay-per-use providers
|
|
4
|
+
* as discoverable capabilities. Each wrapped API becomes a searchable agent.
|
|
5
|
+
* Agents pay per-call via USDC on Base — no upstream API keys needed.
|
|
6
|
+
*
|
|
7
|
+
* Docs: https://beta.paywithlocus.com/skill.md
|
|
8
|
+
* Live index: https://beta.paywithlocus.com/wapi/index.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SourceAdapter, UnifiedAgent, SourceSearchOptions } from "./types.js";
|
|
12
|
+
|
|
13
|
+
const LOCUS_API = "https://beta-api.paywithlocus.com/api";
|
|
14
|
+
const WAPI_INDEX_URL = "https://beta.paywithlocus.com/wapi/index.md";
|
|
15
|
+
|
|
16
|
+
interface LocusProvider {
|
|
17
|
+
name: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
category: string;
|
|
20
|
+
endpoints: string;
|
|
21
|
+
description: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Cache fetched providers — refresh every 10 minutes
|
|
25
|
+
let providerCache: LocusProvider[] | null = null;
|
|
26
|
+
let cacheTime = 0;
|
|
27
|
+
const CACHE_TTL = 10 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse the live markdown index from Locus into structured provider data.
|
|
31
|
+
* The index is a markdown table with columns: Provider, Category, Endpoints, Description
|
|
32
|
+
*/
|
|
33
|
+
function parseMarkdownIndex(md: string): LocusProvider[] {
|
|
34
|
+
const providers: LocusProvider[] = [];
|
|
35
|
+
const lines = md.split("\n");
|
|
36
|
+
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
// Match table rows: | [Provider Name](url) | Category | Endpoints | Description |
|
|
39
|
+
const match = line.match(/^\| \[([^\]]+)\]\(https:\/\/[^)]*\/wapi\/([^.]+)\.md\)\s*\|\s*([^|]+)\|\s*([^|]+)\|\s*([^|]+)\|/);
|
|
40
|
+
if (match) {
|
|
41
|
+
providers.push({
|
|
42
|
+
name: match[1].trim(),
|
|
43
|
+
slug: match[2].trim(),
|
|
44
|
+
category: match[3].trim().toLowerCase(),
|
|
45
|
+
endpoints: match[4].trim(),
|
|
46
|
+
description: match[5].trim(),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return providers;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function fetchProviders(timeout: number): Promise<LocusProvider[]> {
|
|
55
|
+
if (providerCache && Date.now() - cacheTime < CACHE_TTL) {
|
|
56
|
+
return providerCache;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(WAPI_INDEX_URL, {
|
|
61
|
+
signal: AbortSignal.timeout(timeout),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!res.ok) return providerCache ?? [];
|
|
65
|
+
|
|
66
|
+
const md = await res.text();
|
|
67
|
+
const parsed = parseMarkdownIndex(md);
|
|
68
|
+
|
|
69
|
+
if (parsed.length > 0) {
|
|
70
|
+
providerCache = parsed;
|
|
71
|
+
cacheTime = Date.now();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return providerCache ?? [];
|
|
75
|
+
} catch {
|
|
76
|
+
return providerCache ?? [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const locusAdapter: SourceAdapter = {
|
|
81
|
+
name: "locus" as any,
|
|
82
|
+
displayName: "Locus Wrapped APIs",
|
|
83
|
+
|
|
84
|
+
async search(query: string, options?: SourceSearchOptions): Promise<UnifiedAgent[]> {
|
|
85
|
+
const limit = options?.limit ?? 20;
|
|
86
|
+
const timeout = options?.timeout ?? 10000;
|
|
87
|
+
const lower = query.toLowerCase();
|
|
88
|
+
const terms = lower.split(/\s+/).filter(t => t.length > 2);
|
|
89
|
+
|
|
90
|
+
const providers = await fetchProviders(timeout);
|
|
91
|
+
|
|
92
|
+
// Score each provider by relevance
|
|
93
|
+
const scored = providers.map(provider => {
|
|
94
|
+
let score = 0;
|
|
95
|
+
const searchText = `${provider.name} ${provider.description} ${provider.category} ${provider.endpoints}`.toLowerCase();
|
|
96
|
+
|
|
97
|
+
for (const term of terms) {
|
|
98
|
+
if (provider.name.toLowerCase().includes(term)) score += 5;
|
|
99
|
+
if (provider.description.toLowerCase().includes(term)) score += 3;
|
|
100
|
+
if (provider.endpoints.toLowerCase().includes(term)) score += 2;
|
|
101
|
+
if (provider.category.includes(term)) score += 4;
|
|
102
|
+
if (searchText.includes(term)) score += 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { provider, score };
|
|
106
|
+
})
|
|
107
|
+
.filter(s => s.score > 0)
|
|
108
|
+
.sort((a, b) => b.score - a.score)
|
|
109
|
+
.slice(0, limit);
|
|
110
|
+
|
|
111
|
+
return scored.map(s => mapLocusProvider(s.provider));
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async ping(): Promise<boolean> {
|
|
115
|
+
try {
|
|
116
|
+
const res = await fetch(WAPI_INDEX_URL, {
|
|
117
|
+
signal: AbortSignal.timeout(5000),
|
|
118
|
+
});
|
|
119
|
+
return res.ok;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
function mapLocusProvider(provider: LocusProvider): UnifiedAgent {
|
|
127
|
+
const endpointList = provider.endpoints.split(",").map(e => e.trim()).slice(0, 5);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
id: `locus:${provider.slug}`,
|
|
131
|
+
nativeId: provider.slug,
|
|
132
|
+
name: `${provider.name} (via Locus)`,
|
|
133
|
+
description: `${provider.description} Pay-per-use via USDC — no API key needed.`,
|
|
134
|
+
categories: [provider.category.split(" / ")[0].toLowerCase(), "api", "pay-per-use"],
|
|
135
|
+
capabilities: endpointList.map(e => `${provider.name}: ${e}`),
|
|
136
|
+
source: "locus" as any,
|
|
137
|
+
protocol: "http",
|
|
138
|
+
endpoints: {
|
|
139
|
+
http: `${LOCUS_API}/wrapped/${provider.slug}`,
|
|
140
|
+
},
|
|
141
|
+
pricing: {
|
|
142
|
+
model: "per-call",
|
|
143
|
+
amount: "0.01",
|
|
144
|
+
currency: "USDC",
|
|
145
|
+
},
|
|
146
|
+
verified: true,
|
|
147
|
+
lastSeen: Date.now(),
|
|
148
|
+
raw: {
|
|
149
|
+
type: "locus-wrapped-api",
|
|
150
|
+
provider: provider.slug,
|
|
151
|
+
category: provider.category,
|
|
152
|
+
endpoints: endpointList,
|
|
153
|
+
allEndpoints: provider.endpoints,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Olas Mech Marketplace Source Adapter
|
|
3
|
+
* Queries the Olas marketplace subgraph (The Graph) to discover AI agent mechs.
|
|
4
|
+
* Mechs are autonomous agents that accept prompts and return deliverables.
|
|
5
|
+
* Supports Gnosis, Base, Polygon, Optimism chains.
|
|
6
|
+
*
|
|
7
|
+
* Subgraph: https://api.subgraph.autonolas.tech/api/proxy/marketplace-{chain}
|
|
8
|
+
* Docs: https://docs.olas.network
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SourceAdapter, UnifiedAgent, SourceSearchOptions } from "./types.js";
|
|
12
|
+
|
|
13
|
+
const SUBGRAPH_URLS: Record<string, string> = {
|
|
14
|
+
gnosis: "https://api.subgraph.autonolas.tech/api/proxy/marketplace-gnosis",
|
|
15
|
+
base: "https://api.subgraph.autonolas.tech/api/proxy/marketplace-base",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const MECH_AGENTS_QUERY = `{
|
|
19
|
+
mechAgents(first: 50, orderBy: totalTransactions, orderDirection: desc) {
|
|
20
|
+
id
|
|
21
|
+
address
|
|
22
|
+
agentHash
|
|
23
|
+
totalTransactions
|
|
24
|
+
service {
|
|
25
|
+
serviceId
|
|
26
|
+
totalRequests
|
|
27
|
+
totalDeliveries
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}`;
|
|
31
|
+
|
|
32
|
+
interface MechAgent {
|
|
33
|
+
id: string;
|
|
34
|
+
address: string;
|
|
35
|
+
agentHash: string;
|
|
36
|
+
totalTransactions: string;
|
|
37
|
+
service?: {
|
|
38
|
+
serviceId: string;
|
|
39
|
+
totalRequests: string;
|
|
40
|
+
totalDeliveries: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Cache mechs — refresh every 10 minutes
|
|
45
|
+
let mechCache: MechAgent[] | null = null;
|
|
46
|
+
let cacheTime = 0;
|
|
47
|
+
const CACHE_TTL = 10 * 60 * 1000;
|
|
48
|
+
|
|
49
|
+
async function fetchMechs(timeout: number): Promise<MechAgent[]> {
|
|
50
|
+
if (mechCache && Date.now() - cacheTime < CACHE_TTL) {
|
|
51
|
+
return mechCache;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const allMechs: MechAgent[] = [];
|
|
55
|
+
|
|
56
|
+
// Query both Gnosis and Base subgraphs
|
|
57
|
+
for (const [chain, url] of Object.entries(SUBGRAPH_URLS)) {
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch(url, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
body: JSON.stringify({ query: MECH_AGENTS_QUERY }),
|
|
63
|
+
signal: AbortSignal.timeout(timeout),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!res.ok) continue;
|
|
67
|
+
|
|
68
|
+
const json = await res.json();
|
|
69
|
+
const agents = json.data?.mechAgents ?? [];
|
|
70
|
+
|
|
71
|
+
for (const agent of agents) {
|
|
72
|
+
allMechs.push({ ...agent, _chain: chain } as any);
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Chain subgraph unavailable, continue with others
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (allMechs.length > 0) {
|
|
80
|
+
mechCache = allMechs;
|
|
81
|
+
cacheTime = Date.now();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return mechCache ?? [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const olasAdapter: SourceAdapter = {
|
|
88
|
+
name: "olas" as any,
|
|
89
|
+
displayName: "Olas Mech Marketplace",
|
|
90
|
+
|
|
91
|
+
async search(query: string, options?: SourceSearchOptions): Promise<UnifiedAgent[]> {
|
|
92
|
+
const limit = options?.limit ?? 20;
|
|
93
|
+
const timeout = options?.timeout ?? 10000;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const mechs = await fetchMechs(timeout);
|
|
97
|
+
|
|
98
|
+
if (!query) return mechs.slice(0, limit).map(mapMech);
|
|
99
|
+
|
|
100
|
+
// Score mechs by transaction volume and filter by relevance
|
|
101
|
+
// Since mechs don't have descriptions, match against known tool categories
|
|
102
|
+
const lower = query.toLowerCase();
|
|
103
|
+
const relevant = mechs.filter(() => {
|
|
104
|
+
// Olas mechs handle: predictions, research, analysis, trading signals
|
|
105
|
+
const olasKeywords = ["predict", "research", "analyze", "trade", "ai", "agent", "mech", "autonomous", "forecast", "oracle", "data"];
|
|
106
|
+
return olasKeywords.some(k => lower.includes(k)) || true; // Return all if query is general
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return relevant
|
|
110
|
+
.sort((a, b) => parseInt(b.totalTransactions) - parseInt(a.totalTransactions))
|
|
111
|
+
.slice(0, limit)
|
|
112
|
+
.map(mapMech);
|
|
113
|
+
} catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
async ping(): Promise<boolean> {
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch(SUBGRAPH_URLS.gnosis, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: { "Content-Type": "application/json" },
|
|
123
|
+
body: JSON.stringify({ query: "{ global(id: \"global\") { totalMechs } }" }),
|
|
124
|
+
signal: AbortSignal.timeout(5000),
|
|
125
|
+
});
|
|
126
|
+
return res.ok;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
function mapMech(mech: MechAgent): UnifiedAgent {
|
|
134
|
+
const chain = (mech as any)._chain || "gnosis";
|
|
135
|
+
const txCount = parseInt(mech.totalTransactions) || 0;
|
|
136
|
+
const deliveries = parseInt(mech.service?.totalDeliveries || "0");
|
|
137
|
+
const requests = parseInt(mech.service?.totalRequests || "0");
|
|
138
|
+
const successRate = requests > 0 ? Math.round((deliveries / requests) * 100) : 0;
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
id: `olas:${chain}:${mech.address}`,
|
|
142
|
+
nativeId: mech.address,
|
|
143
|
+
name: `Olas Mech ${mech.address.slice(0, 8)}...${mech.address.slice(-4)}`,
|
|
144
|
+
description: `Autonomous AI agent on Olas Marketplace (${chain}). ${txCount} transactions, ${deliveries} deliveries. Accepts prompts and returns AI-powered results via on-chain requests.`,
|
|
145
|
+
categories: ["ai", "autonomous", "prediction"],
|
|
146
|
+
capabilities: [
|
|
147
|
+
"AI prompt execution",
|
|
148
|
+
"Prediction markets",
|
|
149
|
+
"Research and analysis",
|
|
150
|
+
"On-chain autonomous operation",
|
|
151
|
+
],
|
|
152
|
+
source: "olas" as any,
|
|
153
|
+
protocol: "http",
|
|
154
|
+
endpoints: {
|
|
155
|
+
http: `https://mech.tech/mech/${mech.address}`,
|
|
156
|
+
},
|
|
157
|
+
reputation: {
|
|
158
|
+
score: successRate,
|
|
159
|
+
count: deliveries,
|
|
160
|
+
source: `olas-${chain}`,
|
|
161
|
+
},
|
|
162
|
+
pricing: {
|
|
163
|
+
model: "per-call",
|
|
164
|
+
amount: "0.01",
|
|
165
|
+
currency: "OLAS",
|
|
166
|
+
},
|
|
167
|
+
verified: txCount > 10,
|
|
168
|
+
lastSeen: txCount > 0 ? Date.now() : undefined,
|
|
169
|
+
raw: mech,
|
|
170
|
+
};
|
|
171
|
+
}
|
package/src/sources/types.ts
CHANGED
|
@@ -14,7 +14,10 @@ export type AgentSource =
|
|
|
14
14
|
| "adp"
|
|
15
15
|
| "a2a-wellknown"
|
|
16
16
|
| "l402-directory"
|
|
17
|
-
| "skills-registry"
|
|
17
|
+
| "skills-registry"
|
|
18
|
+
| "bankr"
|
|
19
|
+
| "locus"
|
|
20
|
+
| "olas";
|
|
18
21
|
|
|
19
22
|
export type AgentProtocol = "acp" | "mcp" | "x402" | "a2a" | "http" | "l402" | "onchain" | "skill";
|
|
20
23
|
|
package/src/wallet/bridge.ts
CHANGED
|
@@ -19,6 +19,10 @@ const USDC_ADDRESSES: Record<string, Address> = {
|
|
|
19
19
|
"base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
// Kompass treasury address — receives platform fee on payments
|
|
23
|
+
const KOMPASS_TREASURY: Address = "0x2aB73f8CCee4ABE601cE71eCC82Bd9eC375FeBbA";
|
|
24
|
+
const PLATFORM_FEE_BPS = 200; // 2% = 200 basis points
|
|
25
|
+
|
|
22
26
|
// ── ACP Wallet Topup ─────────────────────────────────────
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -29,12 +33,17 @@ export async function topupACPWallet(
|
|
|
29
33
|
wallet: KompassWallet,
|
|
30
34
|
acpWalletAddress: Address,
|
|
31
35
|
amountUSDC: number
|
|
32
|
-
): Promise<{ txHash: Hex; amount: string }> {
|
|
36
|
+
): Promise<{ txHash: Hex; amount: string; fee?: string }> {
|
|
33
37
|
const network = wallet.getNetwork();
|
|
34
38
|
const usdcAddress = USDC_ADDRESSES[network];
|
|
35
39
|
if (!usdcAddress) throw new Error(`No USDC address for network: ${network}`);
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
// Calculate platform fee (2%)
|
|
42
|
+
const feeUSDC = amountUSDC * PLATFORM_FEE_BPS / 10000;
|
|
43
|
+
const netAmount = amountUSDC - feeUSDC;
|
|
44
|
+
const totalNeeded = parseUnits(String(amountUSDC), 6);
|
|
45
|
+
const netAmountRaw = parseUnits(String(netAmount.toFixed(6)), 6);
|
|
46
|
+
const feeAmountRaw = totalNeeded - netAmountRaw;
|
|
38
47
|
|
|
39
48
|
// Check balance first
|
|
40
49
|
const balance = await wallet.getPublicClient().readContract({
|
|
@@ -44,25 +53,43 @@ export async function topupACPWallet(
|
|
|
44
53
|
args: [wallet.getAddress()],
|
|
45
54
|
});
|
|
46
55
|
|
|
47
|
-
if (balance <
|
|
56
|
+
if (balance < totalNeeded) {
|
|
48
57
|
throw new Error(
|
|
49
58
|
`Insufficient USDC: have ${formatUnits(balance, 6)}, need ${amountUSDC}. ` +
|
|
50
59
|
`Fund your Kompass wallet at ${wallet.getAddress()} on ${network}.`
|
|
51
60
|
);
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
// Transfer net amount to ACP wallet
|
|
54
64
|
const txHash = await (wallet.getWalletClient() as any).writeContract({
|
|
55
65
|
address: usdcAddress,
|
|
56
66
|
abi: USDC_ABI,
|
|
57
67
|
functionName: "transfer",
|
|
58
|
-
args: [acpWalletAddress,
|
|
68
|
+
args: [acpWalletAddress, netAmountRaw],
|
|
59
69
|
});
|
|
60
70
|
|
|
61
71
|
await wallet.getPublicClient().waitForTransactionReceipt({ hash: txHash });
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
// Transfer platform fee to Kompass treasury (skip if treasury == ACP wallet)
|
|
74
|
+
if (feeAmountRaw > 0n && KOMPASS_TREASURY.toLowerCase() !== acpWalletAddress.toLowerCase()) {
|
|
75
|
+
try {
|
|
76
|
+
const feeTx = await (wallet.getWalletClient() as any).writeContract({
|
|
77
|
+
address: usdcAddress,
|
|
78
|
+
abi: USDC_ABI,
|
|
79
|
+
functionName: "transfer",
|
|
80
|
+
args: [KOMPASS_TREASURY, feeAmountRaw],
|
|
81
|
+
});
|
|
82
|
+
await wallet.getPublicClient().waitForTransactionReceipt({ hash: feeTx });
|
|
83
|
+
console.log(`[Kompass Bridge] Platform fee: $${feeUSDC.toFixed(4)} USDC → treasury | tx: ${feeTx}`);
|
|
84
|
+
} catch (feeErr) {
|
|
85
|
+
// Fee transfer failure shouldn't block the job
|
|
86
|
+
console.log(`[Kompass Bridge] Fee transfer failed (non-blocking): ${feeErr instanceof Error ? feeErr.message : feeErr}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(`[Kompass Bridge] Transferred ${netAmount.toFixed(4)} USDC to ACP wallet ${acpWalletAddress.slice(0, 10)}... | tx: ${txHash}`);
|
|
64
91
|
|
|
65
|
-
return { txHash, amount: String(
|
|
92
|
+
return { txHash, amount: String(netAmount.toFixed(4)), fee: feeUSDC.toFixed(4) };
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
// ── Lightning Swap (USDC → sats) ─────────────────────────
|
|
@@ -40,9 +40,10 @@ export async function handleACPPayment(
|
|
|
40
40
|
// Job creation (REQUEST) and acceptance (NEGOTIATION) are FREE.
|
|
41
41
|
// We topup only after confirming an agent accepted.
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
43
|
+
// Format requirements to match offering's expected schema.
|
|
44
|
+
// If the offering has a requirement template/schema, try to conform to it.
|
|
45
|
+
// Otherwise fall back to {description: task}.
|
|
46
|
+
const requirements = JSON.stringify(formatRequirements(task, offering));
|
|
46
47
|
|
|
47
48
|
const { stdout } = await execFileAsync(
|
|
48
49
|
"npx",
|
|
@@ -157,3 +158,68 @@ export async function handleACPPayment(
|
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Format task requirements to match the offering's expected schema.
|
|
164
|
+
*
|
|
165
|
+
* ACP agents reject jobs when requirements don't match their offering's
|
|
166
|
+
* expected input format. This function:
|
|
167
|
+
* 1. Checks if the offering has a requirement schema/template
|
|
168
|
+
* 2. Tries to extract relevant fields from the natural language task
|
|
169
|
+
* 3. Falls back to {description: task} if no schema exists
|
|
170
|
+
*/
|
|
171
|
+
function formatRequirements(task: string, offering: any): Record<string, unknown> {
|
|
172
|
+
const schema = offering.requirement ?? offering.requirementSchema ?? offering.inputSchema;
|
|
173
|
+
|
|
174
|
+
// No schema defined — send description (most flexible)
|
|
175
|
+
if (!schema || typeof schema === "string") {
|
|
176
|
+
return { description: task };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Schema is an object — try to populate its fields from the task
|
|
180
|
+
if (typeof schema === "object") {
|
|
181
|
+
const formatted: Record<string, unknown> = {};
|
|
182
|
+
const taskLower = task.toLowerCase();
|
|
183
|
+
|
|
184
|
+
for (const [key, spec] of Object.entries(schema)) {
|
|
185
|
+
const keyLower = key.toLowerCase();
|
|
186
|
+
|
|
187
|
+
// Try to extract Ethereum addresses for address-type fields
|
|
188
|
+
if (keyLower.includes("address") || keyLower.includes("token") || keyLower.includes("contract")) {
|
|
189
|
+
const addrMatch = task.match(/0x[a-fA-F0-9]{40}/);
|
|
190
|
+
if (addrMatch) {
|
|
191
|
+
formatted[key] = addrMatch[0];
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Try to extract chain/network
|
|
197
|
+
if (keyLower.includes("chain") || keyLower.includes("network")) {
|
|
198
|
+
const chains = ["base", "ethereum", "polygon", "arbitrum", "optimism", "bsc"];
|
|
199
|
+
const found = chains.find((c) => taskLower.includes(c));
|
|
200
|
+
formatted[key] = found ?? "base";
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Try to extract numeric amounts
|
|
205
|
+
if (keyLower.includes("amount") || keyLower.includes("quantity") || keyLower.includes("limit")) {
|
|
206
|
+
const numMatch = task.match(/\d+\.?\d*/);
|
|
207
|
+
if (numMatch) {
|
|
208
|
+
formatted[key] = parseFloat(numMatch[0]);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// For any other field, use the task description as the value
|
|
214
|
+
if (typeof spec === "string") {
|
|
215
|
+
formatted[key] = task; // Use full task as the value
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Always include description for context
|
|
220
|
+
formatted.description = task;
|
|
221
|
+
return formatted;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { description: task };
|
|
225
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bankr Payment Handler
|
|
3
|
+
* Routes trading tasks through Bankr's LLM Gateway
|
|
4
|
+
* POST prompt → poll for result
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { KompassWallet } from "../index.js";
|
|
8
|
+
import type { UnifiedAgent } from "../../sources/types.js";
|
|
9
|
+
|
|
10
|
+
const BANKR_API = "https://api.bankr.bot";
|
|
11
|
+
|
|
12
|
+
export async function handleBankrPayment(
|
|
13
|
+
wallet: KompassWallet,
|
|
14
|
+
agent: UnifiedAgent,
|
|
15
|
+
task: string,
|
|
16
|
+
): Promise<{ success: boolean; deliverable?: unknown; jobId?: string }> {
|
|
17
|
+
const apiKey = process.env.BANKR_API_KEY;
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
return {
|
|
20
|
+
success: false,
|
|
21
|
+
deliverable: {
|
|
22
|
+
error: "BANKR_API_KEY not configured. Set the BANKR_API_KEY environment variable.",
|
|
23
|
+
hint: "Get an API key from https://bankr.bot",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const headers: Record<string, string> = {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"X-API-Key": apiKey,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Step 1: Submit prompt
|
|
35
|
+
const submitRes = await fetch(`${BANKR_API}/agent/prompt`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers,
|
|
38
|
+
body: JSON.stringify({ prompt: task }),
|
|
39
|
+
signal: AbortSignal.timeout(30000),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!submitRes.ok) {
|
|
43
|
+
const errBody = await submitRes.text().catch(() => "");
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
deliverable: { error: `Bankr API error: ${submitRes.status}`, details: errBody },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const submitData = await submitRes.json();
|
|
51
|
+
const jobId = submitData.jobId ?? submitData.job_id ?? submitData.id;
|
|
52
|
+
|
|
53
|
+
// If the response is immediate (no job polling needed)
|
|
54
|
+
if (!jobId || submitData.result || submitData.response) {
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
deliverable: submitData.result ?? submitData.response ?? submitData,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Step 2: Poll for result
|
|
62
|
+
const maxAttempts = 30;
|
|
63
|
+
const pollInterval = 2000;
|
|
64
|
+
|
|
65
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
67
|
+
|
|
68
|
+
const pollRes = await fetch(`${BANKR_API}/agent/job/${jobId}`, {
|
|
69
|
+
headers,
|
|
70
|
+
signal: AbortSignal.timeout(10000),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!pollRes.ok) continue;
|
|
74
|
+
|
|
75
|
+
const pollData = await pollRes.json();
|
|
76
|
+
const status = pollData.status?.toLowerCase();
|
|
77
|
+
|
|
78
|
+
if (status === "completed" || status === "done" || status === "success") {
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
deliverable: pollData.result ?? pollData.response ?? pollData,
|
|
82
|
+
jobId,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (status === "failed" || status === "error") {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
deliverable: { error: pollData.error ?? "Job failed", details: pollData },
|
|
90
|
+
jobId,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Still pending, continue polling
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
deliverable: { error: "Job timed out after polling", jobId },
|
|
100
|
+
jobId,
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
deliverable: { error: err instanceof Error ? err.message : String(err) },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|