kompass-sdk 0.1.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.
Files changed (205) hide show
  1. package/dist/a2a/agent-card.d.ts +13 -0
  2. package/dist/a2a/agent-card.d.ts.map +1 -0
  3. package/dist/a2a/agent-card.js +52 -0
  4. package/dist/a2a/agent-card.js.map +1 -0
  5. package/dist/a2a/bridge.d.ts +52 -0
  6. package/dist/a2a/bridge.d.ts.map +1 -0
  7. package/dist/a2a/bridge.js +123 -0
  8. package/dist/a2a/bridge.js.map +1 -0
  9. package/dist/a2a/client.d.ts +34 -0
  10. package/dist/a2a/client.d.ts.map +1 -0
  11. package/dist/a2a/client.js +65 -0
  12. package/dist/a2a/client.js.map +1 -0
  13. package/dist/a2a/server.d.ts +17 -0
  14. package/dist/a2a/server.d.ts.map +1 -0
  15. package/dist/a2a/server.js +194 -0
  16. package/dist/a2a/server.js.map +1 -0
  17. package/dist/abi.d.ts +1068 -0
  18. package/dist/abi.d.ts.map +1 -0
  19. package/dist/abi.js +1372 -0
  20. package/dist/abi.js.map +1 -0
  21. package/dist/adapters/agentkit.d.ts +41 -0
  22. package/dist/adapters/agentkit.d.ts.map +1 -0
  23. package/dist/adapters/agentkit.js +67 -0
  24. package/dist/adapters/agentkit.js.map +1 -0
  25. package/dist/adapters/generic.d.ts +35 -0
  26. package/dist/adapters/generic.d.ts.map +1 -0
  27. package/dist/adapters/generic.js +47 -0
  28. package/dist/adapters/generic.js.map +1 -0
  29. package/dist/adapters/index.d.ts +5 -0
  30. package/dist/adapters/index.d.ts.map +1 -0
  31. package/dist/adapters/index.js +5 -0
  32. package/dist/adapters/index.js.map +1 -0
  33. package/dist/adapters/langchain.d.ts +26 -0
  34. package/dist/adapters/langchain.d.ts.map +1 -0
  35. package/dist/adapters/langchain.js +228 -0
  36. package/dist/adapters/langchain.js.map +1 -0
  37. package/dist/adapters/openclaw.d.ts +18 -0
  38. package/dist/adapters/openclaw.d.ts.map +1 -0
  39. package/dist/adapters/openclaw.js +168 -0
  40. package/dist/adapters/openclaw.js.map +1 -0
  41. package/dist/aggregator.d.ts +36 -0
  42. package/dist/aggregator.d.ts.map +1 -0
  43. package/dist/aggregator.js +168 -0
  44. package/dist/aggregator.js.map +1 -0
  45. package/dist/backends/acp.d.ts +29 -0
  46. package/dist/backends/acp.d.ts.map +1 -0
  47. package/dist/backends/acp.js +126 -0
  48. package/dist/backends/acp.js.map +1 -0
  49. package/dist/backends/types.d.ts +59 -0
  50. package/dist/backends/types.d.ts.map +1 -0
  51. package/dist/backends/types.js +2 -0
  52. package/dist/backends/types.js.map +1 -0
  53. package/dist/bridge.d.ts +35 -0
  54. package/dist/bridge.d.ts.map +1 -0
  55. package/dist/bridge.js +192 -0
  56. package/dist/bridge.js.map +1 -0
  57. package/dist/cli.d.ts +12 -0
  58. package/dist/cli.d.ts.map +1 -0
  59. package/dist/cli.js +331 -0
  60. package/dist/cli.js.map +1 -0
  61. package/dist/discover.d.ts +15 -0
  62. package/dist/discover.d.ts.map +1 -0
  63. package/dist/discover.js +163 -0
  64. package/dist/discover.js.map +1 -0
  65. package/dist/escrow.d.ts +45 -0
  66. package/dist/escrow.d.ts.map +1 -0
  67. package/dist/escrow.js +243 -0
  68. package/dist/escrow.js.map +1 -0
  69. package/dist/index.d.ts +63 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +145 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/intents.d.ts +28 -0
  74. package/dist/intents.d.ts.map +1 -0
  75. package/dist/intents.js +111 -0
  76. package/dist/intents.js.map +1 -0
  77. package/dist/matching.d.ts +29 -0
  78. package/dist/matching.d.ts.map +1 -0
  79. package/dist/matching.js +147 -0
  80. package/dist/matching.js.map +1 -0
  81. package/dist/pipelineAbi.d.ts +113 -0
  82. package/dist/pipelineAbi.d.ts.map +1 -0
  83. package/dist/pipelineAbi.js +74 -0
  84. package/dist/pipelineAbi.js.map +1 -0
  85. package/dist/pipelines.d.ts +42 -0
  86. package/dist/pipelines.d.ts.map +1 -0
  87. package/dist/pipelines.js +185 -0
  88. package/dist/pipelines.js.map +1 -0
  89. package/dist/registry.d.ts +36 -0
  90. package/dist/registry.d.ts.map +1 -0
  91. package/dist/registry.js +187 -0
  92. package/dist/registry.js.map +1 -0
  93. package/dist/reputation.d.ts +10 -0
  94. package/dist/reputation.d.ts.map +1 -0
  95. package/dist/reputation.js +33 -0
  96. package/dist/reputation.js.map +1 -0
  97. package/dist/router.d.ts +72 -0
  98. package/dist/router.d.ts.map +1 -0
  99. package/dist/router.js +190 -0
  100. package/dist/router.js.map +1 -0
  101. package/dist/simple.d.ts +160 -0
  102. package/dist/simple.d.ts.map +1 -0
  103. package/dist/simple.js +237 -0
  104. package/dist/simple.js.map +1 -0
  105. package/dist/sources/a2a-wellknown.d.ts +8 -0
  106. package/dist/sources/a2a-wellknown.d.ts.map +1 -0
  107. package/dist/sources/a2a-wellknown.js +104 -0
  108. package/dist/sources/a2a-wellknown.js.map +1 -0
  109. package/dist/sources/acp.d.ts +7 -0
  110. package/dist/sources/acp.d.ts.map +1 -0
  111. package/dist/sources/acp.js +86 -0
  112. package/dist/sources/acp.js.map +1 -0
  113. package/dist/sources/adp.d.ts +7 -0
  114. package/dist/sources/adp.d.ts.map +1 -0
  115. package/dist/sources/adp.js +59 -0
  116. package/dist/sources/adp.js.map +1 -0
  117. package/dist/sources/erc8004.d.ts +7 -0
  118. package/dist/sources/erc8004.d.ts.map +1 -0
  119. package/dist/sources/erc8004.js +150 -0
  120. package/dist/sources/erc8004.js.map +1 -0
  121. package/dist/sources/index.d.ts +17 -0
  122. package/dist/sources/index.d.ts.map +1 -0
  123. package/dist/sources/index.js +35 -0
  124. package/dist/sources/index.js.map +1 -0
  125. package/dist/sources/kompass-registry.d.ts +8 -0
  126. package/dist/sources/kompass-registry.d.ts.map +1 -0
  127. package/dist/sources/kompass-registry.js +62 -0
  128. package/dist/sources/kompass-registry.js.map +1 -0
  129. package/dist/sources/l402-directory.d.ts +7 -0
  130. package/dist/sources/l402-directory.d.ts.map +1 -0
  131. package/dist/sources/l402-directory.js +42 -0
  132. package/dist/sources/l402-directory.js.map +1 -0
  133. package/dist/sources/mcp-registry.d.ts +8 -0
  134. package/dist/sources/mcp-registry.d.ts.map +1 -0
  135. package/dist/sources/mcp-registry.js +85 -0
  136. package/dist/sources/mcp-registry.js.map +1 -0
  137. package/dist/sources/skills.d.ts +8 -0
  138. package/dist/sources/skills.d.ts.map +1 -0
  139. package/dist/sources/skills.js +153 -0
  140. package/dist/sources/skills.js.map +1 -0
  141. package/dist/sources/types.d.ts +72 -0
  142. package/dist/sources/types.d.ts.map +1 -0
  143. package/dist/sources/types.js +8 -0
  144. package/dist/sources/types.js.map +1 -0
  145. package/dist/sources/x402-ecosystem.d.ts +7 -0
  146. package/dist/sources/x402-ecosystem.d.ts.map +1 -0
  147. package/dist/sources/x402-ecosystem.js +78 -0
  148. package/dist/sources/x402-ecosystem.js.map +1 -0
  149. package/dist/types.d.ts +133 -0
  150. package/dist/types.d.ts.map +1 -0
  151. package/dist/types.js +2 -0
  152. package/dist/types.js.map +1 -0
  153. package/dist/unified.d.ts +90 -0
  154. package/dist/unified.d.ts.map +1 -0
  155. package/dist/unified.js +107 -0
  156. package/dist/unified.js.map +1 -0
  157. package/dist/x402.d.ts +30 -0
  158. package/dist/x402.d.ts.map +1 -0
  159. package/dist/x402.js +79 -0
  160. package/dist/x402.js.map +1 -0
  161. package/package.json +61 -0
  162. package/scripts/bootstrap-agents.mjs +246 -0
  163. package/src/.gitkeep +0 -0
  164. package/src/a2a/agent-card.ts +66 -0
  165. package/src/a2a/bridge.ts +168 -0
  166. package/src/a2a/client.ts +92 -0
  167. package/src/a2a/server.ts +234 -0
  168. package/src/abi.ts +1373 -0
  169. package/src/adapters/agentkit.ts +83 -0
  170. package/src/adapters/generic.ts +62 -0
  171. package/src/adapters/index.ts +4 -0
  172. package/src/adapters/langchain.ts +282 -0
  173. package/src/adapters/openclaw.ts +203 -0
  174. package/src/aggregator.ts +203 -0
  175. package/src/backends/acp.ts +199 -0
  176. package/src/backends/types.ts +78 -0
  177. package/src/bridge.ts +263 -0
  178. package/src/cli.ts +397 -0
  179. package/src/discover.ts +187 -0
  180. package/src/escrow.ts +284 -0
  181. package/src/index.ts +245 -0
  182. package/src/intents.ts +166 -0
  183. package/src/matching.ts +192 -0
  184. package/src/pipelineAbi.ts +74 -0
  185. package/src/pipelines.ts +253 -0
  186. package/src/registry.ts +232 -0
  187. package/src/reputation.ts +43 -0
  188. package/src/router.ts +279 -0
  189. package/src/simple.ts +366 -0
  190. package/src/sources/a2a-wellknown.ts +120 -0
  191. package/src/sources/acp.ts +91 -0
  192. package/src/sources/adp.ts +64 -0
  193. package/src/sources/erc8004.ts +166 -0
  194. package/src/sources/index.ts +52 -0
  195. package/src/sources/kompass-registry.ts +67 -0
  196. package/src/sources/l402-directory.ts +51 -0
  197. package/src/sources/mcp-registry.ts +104 -0
  198. package/src/sources/skills.ts +161 -0
  199. package/src/sources/types.ts +82 -0
  200. package/src/sources/x402-ecosystem.ts +86 -0
  201. package/src/types.ts +147 -0
  202. package/src/unified.ts +155 -0
  203. package/src/x402.ts +122 -0
  204. package/tests/pipelineFlow.test.ts +239 -0
  205. package/tsconfig.json +20 -0
@@ -0,0 +1,187 @@
1
+ import type { KompassConfig, AgentProfile, DiscoverOptions } from "./types.js";
2
+
3
+ /** Execute a GraphQL query against the Ponder indexer */
4
+ async function queryIndexer<T>(config: KompassConfig, query: string, variables?: Record<string, unknown>): Promise<T> {
5
+ const response = await fetch(config.indexerUrl, {
6
+ method: "POST",
7
+ headers: { "Content-Type": "application/json" },
8
+ body: JSON.stringify({ query, variables }),
9
+ });
10
+
11
+ if (!response.ok) {
12
+ throw new Error(`Indexer query failed: ${response.status} ${response.statusText}`);
13
+ }
14
+
15
+ const result = await response.json() as { data?: T; errors?: Array<{ message: string }> };
16
+
17
+ if (result.errors?.length) {
18
+ throw new Error(`Indexer GraphQL error: ${result.errors[0].message}`);
19
+ }
20
+
21
+ if (!result.data) {
22
+ throw new Error("Indexer returned no data");
23
+ }
24
+
25
+ return result.data;
26
+ }
27
+
28
+ /** Parse raw agent data from GraphQL into AgentProfile */
29
+ function parseAgent(raw: Record<string, unknown>): AgentProfile {
30
+ const paymentMode = (raw.paymentMode as AgentProfile["paymentMode"]) ?? "unknown";
31
+
32
+ return {
33
+ id: raw.id as AgentProfile["id"],
34
+ ensNode: raw.ensNode as `0x${string}`,
35
+ ensName: (raw.ensName as string) ?? "",
36
+ agentType: (raw.agentType as AgentProfile["agentType"]) ?? "unknown",
37
+ categories: (raw.categories as string) ?? "",
38
+ pricingModel: (raw.pricingModel as string) ?? "",
39
+ pricingRate: (raw.pricingRate as string) ?? "",
40
+ pricingToken: (raw.pricingToken as string) ?? "",
41
+ erc8004Id: (raw.erc8004Id as string) ?? "",
42
+ identityVerification: (raw.identityVerification as string) ?? "",
43
+ identityVerified: Boolean(raw.identityVerified),
44
+ paymentMode,
45
+ endpointMcp: (raw.endpointMcp as string) ?? "",
46
+ endpointA2a: (raw.endpointA2a as string) ?? "",
47
+ endpointX402: (raw.endpointX402 as string) ?? "",
48
+ x402Enabled: Boolean(raw.x402Enabled),
49
+ evaluatorDomains: (raw.evaluatorDomains as string) ?? "",
50
+ description: (raw.description as string) ?? "",
51
+ registered: raw.registered as boolean,
52
+ registeredAt: BigInt(raw.registeredAt as string),
53
+ updatedAt: BigInt(raw.updatedAt as string),
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Discover agents matching criteria via the indexer's GraphQL API.
59
+ * Filters by category (substring match), agent type, and registration status.
60
+ */
61
+ export async function discover(config: KompassConfig, options: DiscoverOptions = {}): Promise<AgentProfile[]> {
62
+ const {
63
+ category,
64
+ evaluatorDomain,
65
+ agentType,
66
+ paymentMode,
67
+ requiresX402,
68
+ verifiedOnly,
69
+ limit = 50,
70
+ } = options;
71
+
72
+ // Build where clause for Ponder GraphQL
73
+ const conditions: string[] = ['registered: true'];
74
+ if (agentType) {
75
+ conditions.push(`agentType: ${agentType}`);
76
+ }
77
+ if (category) {
78
+ conditions.push(`categories_contains: "${category}"`);
79
+ }
80
+ if (evaluatorDomain) {
81
+ conditions.push(`evaluatorDomains_contains: "${evaluatorDomain}"`);
82
+ }
83
+ if (paymentMode) {
84
+ conditions.push(`paymentMode: "${paymentMode}"`);
85
+ }
86
+ if (requiresX402) {
87
+ conditions.push("x402Enabled: true");
88
+ }
89
+ if (verifiedOnly) {
90
+ conditions.push("identityVerified: true");
91
+ }
92
+
93
+ const where = conditions.length > 0 ? `where: { ${conditions.join(", ")} }` : "";
94
+
95
+ const query = `
96
+ query {
97
+ agents(${where}, limit: ${limit}) {
98
+ items {
99
+ id
100
+ ensNode
101
+ ensName
102
+ agentType
103
+ categories
104
+ pricingModel
105
+ pricingRate
106
+ pricingToken
107
+ erc8004Id
108
+ identityVerification
109
+ identityVerified
110
+ paymentMode
111
+ endpointMcp
112
+ endpointA2a
113
+ endpointX402
114
+ x402Enabled
115
+ evaluatorDomains
116
+ description
117
+ registered
118
+ registeredAt
119
+ updatedAt
120
+ }
121
+ }
122
+ }
123
+ `;
124
+
125
+ const data = await queryIndexer<{ agents: { items: Record<string, unknown>[] } }>(config, query);
126
+ let agents = data.agents.items.map(parseAgent);
127
+
128
+ // Client-side filtering for maxPrice (indexer stores as string)
129
+ if (options.maxPrice !== undefined) {
130
+ agents = agents.filter((a) => {
131
+ if (!a.pricingRate) return true;
132
+ try {
133
+ return BigInt(a.pricingRate) <= options.maxPrice!;
134
+ } catch {
135
+ return true;
136
+ }
137
+ });
138
+ }
139
+
140
+ return agents;
141
+ }
142
+
143
+ /**
144
+ * Get a single agent's profile by address.
145
+ */
146
+ export async function getAgent(config: KompassConfig, address: string): Promise<AgentProfile | null> {
147
+ const query = `
148
+ query {
149
+ agent(id: "${address.toLowerCase()}") {
150
+ id
151
+ ensNode
152
+ ensName
153
+ agentType
154
+ categories
155
+ pricingModel
156
+ pricingRate
157
+ pricingToken
158
+ erc8004Id
159
+ identityVerification
160
+ identityVerified
161
+ paymentMode
162
+ endpointMcp
163
+ endpointA2a
164
+ endpointX402
165
+ x402Enabled
166
+ evaluatorDomains
167
+ description
168
+ registered
169
+ registeredAt
170
+ updatedAt
171
+ }
172
+ }
173
+ `;
174
+
175
+ const data = await queryIndexer<{ agent: Record<string, unknown> | null }>(config, query);
176
+ return data.agent ? parseAgent(data.agent) : null;
177
+ }
178
+
179
+ /**
180
+ * Find evaluators by domain specialization.
181
+ */
182
+ export async function findEvaluator(config: KompassConfig, domain: string): Promise<AgentProfile[]> {
183
+ return discover(config, {
184
+ category: domain,
185
+ agentType: "evaluator",
186
+ });
187
+ }
package/src/escrow.ts ADDED
@@ -0,0 +1,284 @@
1
+ import { type Address, type Hex, getContract, parseEventLogs, zeroHash } from "viem";
2
+ import { KompassEscrowAbi } from "./abi.js";
3
+ import type { KompassConfig, CreateJobOptions, CreateJobResult, Job, JobStatus, MilestoneData, MilestoneStatus, TxResult } from "./types.js";
4
+
5
+ const JOB_STATUS_MAP: Record<number, JobStatus> = {
6
+ 0: "Open",
7
+ 1: "Funded",
8
+ 2: "Submitted",
9
+ 3: "Completed",
10
+ 4: "Rejected",
11
+ 5: "Expired",
12
+ };
13
+
14
+ const MILESTONE_STATUS_MAP: Record<number, MilestoneStatus> = {
15
+ 0: "Pending",
16
+ 1: "Submitted",
17
+ 2: "Approved",
18
+ 3: "Rejected",
19
+ };
20
+
21
+ function requireWallet(config: KompassConfig) {
22
+ if (!config.walletClient) {
23
+ throw new Error("WalletClient required for write operations");
24
+ }
25
+ return config.walletClient;
26
+ }
27
+
28
+ /**
29
+ * Create a new job on the escrow contract.
30
+ * Supports both single-deliverable and multi-milestone jobs.
31
+ */
32
+ export async function createJob(config: KompassConfig, options: CreateJobOptions): Promise<CreateJobResult> {
33
+ const wallet = requireWallet(config);
34
+ const hook = options.hook ?? config.defaultHookAddress ?? ("0x0000000000000000000000000000000000000000" as Address);
35
+
36
+ let txHash: Hex;
37
+
38
+ if (options.milestones && options.milestones.length > 0) {
39
+ const shares = options.milestones.map((m) => m.budgetShareBps);
40
+ txHash = await wallet.writeContract({
41
+ address: config.escrowAddress,
42
+ abi: KompassEscrowAbi,
43
+ functionName: "createJobWithMilestones",
44
+ args: [
45
+ options.provider,
46
+ options.evaluator,
47
+ options.expiredAt,
48
+ options.description,
49
+ hook,
50
+ shares,
51
+ ],
52
+ });
53
+ } else {
54
+ txHash = await wallet.writeContract({
55
+ address: config.escrowAddress,
56
+ abi: KompassEscrowAbi,
57
+ functionName: "createJob",
58
+ args: [
59
+ options.provider,
60
+ options.evaluator,
61
+ options.expiredAt,
62
+ options.description,
63
+ hook,
64
+ ],
65
+ });
66
+ }
67
+
68
+ // Wait for receipt and extract jobId from JobCreated event
69
+ const receipt = await config.publicClient.waitForTransactionReceipt({ hash: txHash });
70
+
71
+ const logs = parseEventLogs({
72
+ abi: KompassEscrowAbi,
73
+ logs: receipt.logs,
74
+ eventName: "JobCreated",
75
+ });
76
+
77
+ if (logs.length === 0) {
78
+ throw new Error("JobCreated event not found in transaction receipt");
79
+ }
80
+
81
+ return {
82
+ txHash,
83
+ jobId: logs[0].args.jobId,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Set the budget for an Open job.
89
+ */
90
+ export async function setBudget(config: KompassConfig, jobId: bigint, amount: bigint): Promise<TxResult> {
91
+ const wallet = requireWallet(config);
92
+ const txHash = await wallet.writeContract({
93
+ address: config.escrowAddress,
94
+ abi: KompassEscrowAbi,
95
+ functionName: "setBudget",
96
+ args: [jobId, amount, "0x"],
97
+ });
98
+ await config.publicClient.waitForTransactionReceipt({ hash: txHash });
99
+ return { txHash };
100
+ }
101
+
102
+ /**
103
+ * Fund a job, transitioning it from Open to Funded.
104
+ * Reads the job's budget for the expectedBudget parameter.
105
+ * Caller must have approved the escrow contract for the budget amount first.
106
+ */
107
+ export async function fund(config: KompassConfig, jobId: bigint): Promise<TxResult> {
108
+ const wallet = requireWallet(config);
109
+
110
+ // Read current budget for front-running protection
111
+ const jobData = await config.publicClient.readContract({
112
+ address: config.escrowAddress,
113
+ abi: KompassEscrowAbi,
114
+ functionName: "jobs",
115
+ args: [jobId],
116
+ });
117
+ const budget = jobData[4]; // budget is index 4
118
+
119
+ if (budget === 0n) {
120
+ throw new Error("Job budget is 0 — set budget before funding");
121
+ }
122
+
123
+ const txHash = await wallet.writeContract({
124
+ address: config.escrowAddress,
125
+ abi: KompassEscrowAbi,
126
+ functionName: "fund",
127
+ args: [jobId, budget, "0x"],
128
+ });
129
+ await config.publicClient.waitForTransactionReceipt({ hash: txHash });
130
+ return { txHash };
131
+ }
132
+
133
+ /**
134
+ * Submit a deliverable for a job or milestone.
135
+ * If milestoneIndex is provided, calls submitMilestone; otherwise calls submit.
136
+ */
137
+ export async function submit(
138
+ config: KompassConfig,
139
+ jobId: bigint,
140
+ deliverable: Hex,
141
+ milestoneIndex?: number
142
+ ): Promise<TxResult> {
143
+ const wallet = requireWallet(config);
144
+ let txHash: Hex;
145
+
146
+ if (milestoneIndex !== undefined) {
147
+ txHash = await wallet.writeContract({
148
+ address: config.escrowAddress,
149
+ abi: KompassEscrowAbi,
150
+ functionName: "submitMilestone",
151
+ args: [jobId, BigInt(milestoneIndex), deliverable],
152
+ });
153
+ } else {
154
+ txHash = await wallet.writeContract({
155
+ address: config.escrowAddress,
156
+ abi: KompassEscrowAbi,
157
+ functionName: "submit",
158
+ args: [jobId, deliverable, "0x"],
159
+ });
160
+ }
161
+
162
+ await config.publicClient.waitForTransactionReceipt({ hash: txHash });
163
+ return { txHash };
164
+ }
165
+
166
+ /**
167
+ * Complete a job or milestone (evaluator action).
168
+ * If milestoneIndex is provided, calls completeMilestone; otherwise calls complete.
169
+ */
170
+ export async function complete(
171
+ config: KompassConfig,
172
+ jobId: bigint,
173
+ reason?: Hex,
174
+ milestoneIndex?: number
175
+ ): Promise<TxResult> {
176
+ const wallet = requireWallet(config);
177
+ const reasonHash = reason ?? zeroHash;
178
+ let txHash: Hex;
179
+
180
+ if (milestoneIndex !== undefined) {
181
+ txHash = await wallet.writeContract({
182
+ address: config.escrowAddress,
183
+ abi: KompassEscrowAbi,
184
+ functionName: "completeMilestone",
185
+ args: [jobId, BigInt(milestoneIndex), reasonHash],
186
+ });
187
+ } else {
188
+ txHash = await wallet.writeContract({
189
+ address: config.escrowAddress,
190
+ abi: KompassEscrowAbi,
191
+ functionName: "complete",
192
+ args: [jobId, reasonHash, "0x"],
193
+ });
194
+ }
195
+
196
+ await config.publicClient.waitForTransactionReceipt({ hash: txHash });
197
+ return { txHash };
198
+ }
199
+
200
+ /**
201
+ * Complete a job with partial payment (evaluator action).
202
+ * Releases percentageBps of the budget to the provider, refunds the rest.
203
+ */
204
+ export async function completePartial(
205
+ config: KompassConfig,
206
+ jobId: bigint,
207
+ percentageBps: number,
208
+ reason?: Hex
209
+ ): Promise<TxResult> {
210
+ const wallet = requireWallet(config);
211
+ const reasonHash = reason ?? zeroHash;
212
+
213
+ const txHash = await wallet.writeContract({
214
+ address: config.escrowAddress,
215
+ abi: KompassEscrowAbi,
216
+ functionName: "completePartial",
217
+ args: [jobId, percentageBps, reasonHash],
218
+ });
219
+ await config.publicClient.waitForTransactionReceipt({ hash: txHash });
220
+ return { txHash };
221
+ }
222
+
223
+ /**
224
+ * Reject a job (client in Open state, evaluator in Funded/Submitted state).
225
+ */
226
+ export async function reject(config: KompassConfig, jobId: bigint, reason?: Hex): Promise<TxResult> {
227
+ const wallet = requireWallet(config);
228
+ const reasonHash = reason ?? zeroHash;
229
+
230
+ const txHash = await wallet.writeContract({
231
+ address: config.escrowAddress,
232
+ abi: KompassEscrowAbi,
233
+ functionName: "reject",
234
+ args: [jobId, reasonHash, "0x"],
235
+ });
236
+ await config.publicClient.waitForTransactionReceipt({ hash: txHash });
237
+ return { txHash };
238
+ }
239
+
240
+ /**
241
+ * Read a job's full data from the contract.
242
+ */
243
+ export async function getJob(config: KompassConfig, jobId: bigint): Promise<Job> {
244
+ const data = await config.publicClient.readContract({
245
+ address: config.escrowAddress,
246
+ abi: KompassEscrowAbi,
247
+ functionName: "jobs",
248
+ args: [jobId],
249
+ });
250
+
251
+ // Solidity struct auto-getter returns a tuple:
252
+ // (client, provider, evaluator, hook, budget, expiredAt, status, description, deliverable, milestoneCount, currentMilestone)
253
+ return {
254
+ client: data[0] as Address,
255
+ provider: data[1] as Address,
256
+ evaluator: data[2] as Address,
257
+ hook: data[3] as Address,
258
+ budget: data[4] as bigint,
259
+ expiredAt: data[5] as bigint,
260
+ status: JOB_STATUS_MAP[Number(data[6])] ?? "Open",
261
+ description: data[7] as string,
262
+ deliverable: data[8] as Hex,
263
+ milestoneCount: Number(data[9]),
264
+ currentMilestone: Number(data[10]),
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Read milestones for a job.
270
+ */
271
+ export async function getMilestones(config: KompassConfig, jobId: bigint): Promise<MilestoneData[]> {
272
+ const data = await config.publicClient.readContract({
273
+ address: config.escrowAddress,
274
+ abi: KompassEscrowAbi,
275
+ functionName: "getMilestones",
276
+ args: [jobId],
277
+ });
278
+
279
+ return (data as Array<{ budgetShareBps: number; deliverable: Hex; status: number }>).map((ms) => ({
280
+ budgetShareBps: Number(ms.budgetShareBps),
281
+ deliverable: ms.deliverable,
282
+ status: MILESTONE_STATUS_MAP[Number(ms.status)] ?? "Pending",
283
+ }));
284
+ }