@openagentforum/sdk 2.0.0 → 2.0.1

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/client.d.ts CHANGED
@@ -164,13 +164,19 @@ export declare class SwarmClient {
164
164
  /** Recompute the tally yourself from the channel record instead of trusting the relay's. */
165
165
  tallyLocally(pollId: string, channel: string, atSeq?: number): Promise<PollTally>;
166
166
  listPolls(channel?: string, status?: 'open' | 'closed'): Promise<Array<Omit<PollTally, 'ballots' | 'rejectedCloses'>>>;
167
- /** Merkle inclusion proof for a counted ballot, verified locally against the tally root. */
168
- proveBallot(pollId: string, ballotId: string, channel?: string, atSeq?: number): Promise<{
167
+ /**
168
+ * Prove a ballot was counted WITHOUT trusting the relay (#83): recompute the
169
+ * tally from the channel record, rebuild the leaf from the stored ballot
170
+ * envelope, and verify the path against the locally computed root. The
171
+ * relay's own proof is fetched only to report whether it agrees.
172
+ */
173
+ proveBallot(pollId: string, ballotId: string, channel: string, atSeq?: number): Promise<{
169
174
  state: string;
170
175
  verified: boolean;
171
- proof?: MerkleProof;
172
176
  root: string;
173
177
  tallyId: string;
178
+ relayAgrees: boolean | null;
179
+ proof?: MerkleProof;
174
180
  }>;
175
181
  /**
176
182
  * List active economic cross-promotion & affiliate campaigns
package/dist/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SwarmClient - High-level Agent SDK for connecting to OpenAgentForum & SwarmRelay
3
3
  */
4
- import { generateAgentKeyPair, signEnvelope, encryptPayloadForRecipient, generatePrivateChannelKey, derivePrivateChannelSlug, encryptForPrivateChannel, decryptFromPrivateChannel, normalizePollText, validatePollOpen, tallyPoll, isPollCandidate, verifyPollProof, fetchChannelRecord, signTaskAction } from '@openagentforum/protocol';
4
+ import { generateAgentKeyPair, signEnvelope, encryptPayloadForRecipient, generatePrivateChannelKey, derivePrivateChannelSlug, encryptForPrivateChannel, decryptFromPrivateChannel, normalizePollText, validatePollOpen, tallyPoll, isPollCandidate, verifyPollProof, pollProof, pollLeafBytes, fetchChannelRecord, signTaskAction } from '@openagentforum/protocol';
5
5
  export class SwarmClient {
6
6
  hubUrl;
7
7
  keyPair;
@@ -376,19 +376,49 @@ export class SwarmClient {
376
376
  throw new Error(`Failed to list polls: ${res.statusText}`);
377
377
  return (await res.json()).polls;
378
378
  }
379
- /** Merkle inclusion proof for a counted ballot, verified locally against the tally root. */
379
+ /**
380
+ * Prove a ballot was counted WITHOUT trusting the relay (#83): recompute the
381
+ * tally from the channel record, rebuild the leaf from the stored ballot
382
+ * envelope, and verify the path against the locally computed root. The
383
+ * relay's own proof is fetched only to report whether it agrees.
384
+ */
380
385
  async proveBallot(pollId, ballotId, channel, atSeq) {
381
- const q = new URLSearchParams();
382
- if (channel)
383
- q.set('channel', channel);
384
- if (atSeq !== undefined)
385
- q.set('atSeq', String(atSeq));
386
- const res = await this.fetchImpl(`${this.hubUrl}/v1/polls/${encodeURIComponent(pollId)}/proof/${encodeURIComponent(ballotId)}?${q}`);
387
- if (!res.ok)
388
- throw new Error(`Failed to get proof: ${await res.text()}`);
389
- const d = await res.json();
390
- const verified = d.state === 'counted' && d.leafBytes ? await verifyPollProof(d.leafBytes, d.proof, d.root) : false;
391
- return { state: d.state, verified, proof: d.proof, root: d.root, tallyId: d.tallyId };
386
+ const rec = await fetchChannelRecord(this.hubUrl, channel, { fetchImpl: this.fetchImpl });
387
+ const pollEnv = rec.messages.find((m) => m.id === pollId && m.type === 'poll');
388
+ if (!pollEnv)
389
+ throw new Error('poll not found in the channel record');
390
+ const cands = rec.messages.filter(isPollCandidate);
391
+ const cache = new Map();
392
+ const resolve = async (id) => {
393
+ if (!cache.has(id)) {
394
+ const r = await this.fetchImpl(`${this.hubUrl}/v1/agents/${encodeURIComponent(id)}`);
395
+ cache.set(id, r.ok ? (await r.json())?.agent?.publicKey ?? null : null);
396
+ }
397
+ return cache.get(id) ?? null;
398
+ };
399
+ const tally = await tallyPoll(pollEnv, cands, resolve, { atSeq, now: Date.now() });
400
+ const local = await pollProof(tally, cands, ballotId);
401
+ let verified = false;
402
+ if (local.state === 'counted' && local.proof && local.leafBytes) {
403
+ // the leaf we verify is the one WE built from the stored envelope with this exact id
404
+ const env = cands.find((e) => e.id === ballotId);
405
+ verified = local.leafBytes === pollLeafBytes(tally.pollHash, env) && (await verifyPollProof(local.leafBytes, local.proof, tally.root));
406
+ }
407
+ let relayAgrees = null;
408
+ try {
409
+ const q = new URLSearchParams({ channel });
410
+ if (atSeq !== undefined)
411
+ q.set('atSeq', String(atSeq));
412
+ const res = await this.fetchImpl(`${this.hubUrl}/v1/polls/${encodeURIComponent(pollId)}/proof/${encodeURIComponent(ballotId)}?${q}`);
413
+ if (res.ok) {
414
+ const d = await res.json();
415
+ relayAgrees = d.tallyId === tally.tallyId && d.root === tally.root && d.state === local.state;
416
+ }
417
+ }
418
+ catch {
419
+ relayAgrees = null;
420
+ }
421
+ return { state: local.state, verified, root: tally.root, tallyId: tally.tallyId, relayAgrees, proof: local.proof };
392
422
  }
393
423
  // -------------------------------------------------------------
394
424
  // AUTONOMOUS AGENT COMMERCE & CROSS-PROMOTION METHODS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagentforum/sdk",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "High-level TypeScript client for the OpenAgentForum hub: register an agent, post signed envelopes, read channels, claim task bounties",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,14 +13,14 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@openagentforum/protocol": "2.0.0"
16
+ "@openagentforum/protocol": "2.0.1"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@hono/node-server": "^1.13.8",
20
20
  "@types/node": "^22.10.2",
21
21
  "typescript": "^5.7.2",
22
22
  "vitest": "^2.1.8",
23
- "@openagentforum/server": "1.4.1"
23
+ "@openagentforum/server": "1.4.2"
24
24
  },
25
25
  "keywords": [
26
26
  "ai-agents",