@picoads/eliza-plugin 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.
package/dist/index.js ADDED
@@ -0,0 +1,1067 @@
1
+ // src/services/picoads.ts
2
+ import crypto from "crypto";
3
+ import { Service } from "@elizaos/core";
4
+ import { privateKeyToAccount } from "viem/accounts";
5
+ import { getAddress } from "viem";
6
+ var PicoadsService = class _PicoadsService extends Service {
7
+ static serviceType = "PICOADS";
8
+ capabilityDescription = "Connects to the picoads micro ad network \u2014 handles EIP-191 auth, x402 payments, and marketplace interactions.";
9
+ apiUrl;
10
+ account;
11
+ apiKey = null;
12
+ _agentId;
13
+ termsAccepted = false;
14
+ // Provider caches
15
+ marketCache = null;
16
+ stateCache = null;
17
+ CACHE_TTL_MS = 6e4;
18
+ get agentId() {
19
+ return this._agentId;
20
+ }
21
+ static async start(runtime) {
22
+ const service = new _PicoadsService(runtime);
23
+ await service.initialize(runtime);
24
+ return service;
25
+ }
26
+ async stop() {
27
+ this.marketCache = null;
28
+ this.stateCache = null;
29
+ }
30
+ async initialize(runtime) {
31
+ this.apiUrl = runtime.getSetting("PICOADS_API_URL") ?? "https://picoads.xyz";
32
+ const privateKey = runtime.getSetting("EVM_PRIVATE_KEY");
33
+ if (!privateKey)
34
+ throw new Error("EVM_PRIVATE_KEY required in character settings");
35
+ this.account = privateKeyToAccount(privateKey);
36
+ this._agentId = getAddress(this.account.address);
37
+ const memories = await runtime.getMemories({
38
+ tableName: "picoads_state"
39
+ });
40
+ const regMemory = memories?.find(
41
+ (m) => {
42
+ const meta = m.content?.metadata;
43
+ return meta?.type === "registration";
44
+ }
45
+ );
46
+ const regMeta = regMemory?.content?.metadata;
47
+ if (regMeta?.apiKey) {
48
+ this.apiKey = regMeta.apiKey;
49
+ try {
50
+ const resp = await fetch(
51
+ `${this.apiUrl}/agents/${this._agentId}/profile`
52
+ );
53
+ if (!resp.ok) {
54
+ this.apiKey = null;
55
+ }
56
+ } catch {
57
+ this.apiKey = null;
58
+ }
59
+ }
60
+ }
61
+ // --- Registration state ---
62
+ getRegistrationState() {
63
+ return { registered: this.apiKey !== null, apiKey: this.apiKey };
64
+ }
65
+ setApiKey(key) {
66
+ this.apiKey = key;
67
+ }
68
+ hasAcceptedTerms() {
69
+ return this.termsAccepted;
70
+ }
71
+ markTermsAccepted() {
72
+ this.termsAccepted = true;
73
+ }
74
+ // --- Provider caches ---
75
+ getMarketCache() {
76
+ if (this.marketCache && Date.now() < this.marketCache.expiresAt) {
77
+ return this.marketCache.data;
78
+ }
79
+ this.marketCache = null;
80
+ return null;
81
+ }
82
+ setMarketCache(data) {
83
+ this.marketCache = { data, expiresAt: Date.now() + this.CACHE_TTL_MS };
84
+ }
85
+ getStateCache() {
86
+ if (this.stateCache && Date.now() < this.stateCache.expiresAt) {
87
+ return this.stateCache.data;
88
+ }
89
+ this.stateCache = null;
90
+ return null;
91
+ }
92
+ setStateCache(data) {
93
+ this.stateCache = { data, expiresAt: Date.now() + this.CACHE_TTL_MS };
94
+ }
95
+ invalidateStateCache() {
96
+ this.stateCache = null;
97
+ }
98
+ // --- EIP-191 signing ---
99
+ async signEip191(method, path) {
100
+ const nonce = crypto.randomBytes(16).toString("hex");
101
+ const timestamp = Math.floor(Date.now() / 1e3).toString();
102
+ const message = `picoads:${method}:${path}:${nonce}:${timestamp}`;
103
+ const signature = await this.account.signMessage({ message });
104
+ return {
105
+ header: `EIP191 ${this._agentId}:${nonce}:${timestamp}:${signature}`,
106
+ nonce,
107
+ timestamp
108
+ };
109
+ }
110
+ // --- API calls ---
111
+ async apiCall(method, path, body) {
112
+ const headers = {
113
+ "Content-Type": "application/json"
114
+ };
115
+ if (method !== "GET") {
116
+ const auth = await this.signEip191(method, path);
117
+ headers["Authorization"] = auth.header;
118
+ }
119
+ let resp = await fetch(`${this.apiUrl}${path}`, {
120
+ method,
121
+ headers,
122
+ body: body ? JSON.stringify(body) : void 0
123
+ });
124
+ if (resp.status === 429) {
125
+ const retryAfter = Number(resp.headers.get("retry-after") ?? "5");
126
+ await new Promise((r) => setTimeout(r, retryAfter * 1e3));
127
+ if (method !== "GET") {
128
+ const auth = await this.signEip191(method, path);
129
+ headers["Authorization"] = auth.header;
130
+ }
131
+ resp = await fetch(`${this.apiUrl}${path}`, {
132
+ method,
133
+ headers,
134
+ body: body ? JSON.stringify(body) : void 0
135
+ });
136
+ }
137
+ return resp;
138
+ }
139
+ // --- x402 payment ---
140
+ async payX402(endpoint, body) {
141
+ const probeResp = await this.apiCall("POST", endpoint, body);
142
+ if (probeResp.status !== 402) {
143
+ const data = await probeResp.json().catch(() => ({}));
144
+ return {
145
+ success: probeResp.ok,
146
+ data,
147
+ error: probeResp.ok ? void 0 : data.error
148
+ };
149
+ }
150
+ const paymentRequiredHeader = probeResp.headers.get("payment-required");
151
+ if (!paymentRequiredHeader) {
152
+ return { success: false, error: "Got 402 but no PAYMENT-REQUIRED header" };
153
+ }
154
+ const paymentRequired = JSON.parse(
155
+ Buffer.from(paymentRequiredHeader, "base64").toString()
156
+ );
157
+ const requirements = paymentRequired.accepts[0];
158
+ const validAfter = 0;
159
+ const validBefore = Math.floor(Date.now() / 1e3) + 3600;
160
+ const nonce = `0x${crypto.randomBytes(32).toString("hex")}`;
161
+ const domain = {
162
+ name: requirements.extra?.name ?? "USD Coin",
163
+ version: requirements.extra?.version ?? "2",
164
+ chainId: 8453,
165
+ verifyingContract: requirements.asset
166
+ };
167
+ const types = {
168
+ TransferWithAuthorization: [
169
+ { name: "from", type: "address" },
170
+ { name: "to", type: "address" },
171
+ { name: "value", type: "uint256" },
172
+ { name: "validAfter", type: "uint256" },
173
+ { name: "validBefore", type: "uint256" },
174
+ { name: "nonce", type: "bytes32" }
175
+ ]
176
+ };
177
+ const typedMessage = {
178
+ from: this.account.address,
179
+ to: requirements.payTo,
180
+ value: BigInt(requirements.amount),
181
+ validAfter: BigInt(validAfter),
182
+ validBefore: BigInt(validBefore),
183
+ nonce
184
+ };
185
+ const signature = await this.account.signTypedData({
186
+ domain,
187
+ types,
188
+ primaryType: "TransferWithAuthorization",
189
+ message: typedMessage
190
+ });
191
+ const paymentPayload = {
192
+ x402Version: 2,
193
+ resource: paymentRequired.resource,
194
+ accepted: requirements,
195
+ payload: {
196
+ signature,
197
+ authorization: {
198
+ from: this.account.address,
199
+ to: requirements.payTo,
200
+ value: requirements.amount,
201
+ validAfter: validAfter.toString(),
202
+ validBefore: validBefore.toString(),
203
+ nonce
204
+ }
205
+ }
206
+ };
207
+ const paymentSignatureHeader = Buffer.from(
208
+ JSON.stringify(paymentPayload)
209
+ ).toString("base64");
210
+ const auth = await this.signEip191("POST", endpoint);
211
+ const finalResp = await fetch(`${this.apiUrl}${endpoint}`, {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ Authorization: auth.header,
216
+ "PAYMENT-SIGNATURE": paymentSignatureHeader
217
+ },
218
+ body: body ? JSON.stringify(body) : void 0
219
+ });
220
+ const finalData = await finalResp.json().catch(() => ({}));
221
+ return {
222
+ success: finalResp.ok,
223
+ data: finalData,
224
+ error: finalResp.ok ? void 0 : "Payment failed"
225
+ };
226
+ }
227
+ };
228
+
229
+ // src/actions/register.ts
230
+ var registerAction = {
231
+ name: "REGISTER_AGENT",
232
+ description: "Register on picoads to start advertising or publishing. Costs $1 USDC on Base.",
233
+ similes: [
234
+ "register on picoads",
235
+ "sign up for picoads",
236
+ "create picoads account",
237
+ "join picoads",
238
+ "join the ad network"
239
+ ],
240
+ validate: async (runtime) => {
241
+ const service = runtime.getService("PICOADS");
242
+ if (!service) return false;
243
+ return !service.getRegistrationState().registered;
244
+ },
245
+ handler: async (runtime, message, _state, _options, callback) => {
246
+ const service = runtime.getService("PICOADS");
247
+ if (!service) return { success: false, error: "PicoadsService not available" };
248
+ const result = await service.payX402("/agents/register", {
249
+ name: `agent-${service.agentId.slice(0, 8)}`,
250
+ description: "Eliza agent",
251
+ wallet: service.agentId,
252
+ source: "eliza",
253
+ registrationFile: {
254
+ name: `agent-${service.agentId.slice(0, 8)}`,
255
+ description: "Eliza agent"
256
+ }
257
+ });
258
+ if (!result.success) {
259
+ callback?.({ text: `Registration failed: ${result.error}` });
260
+ return { success: false, error: `Registration failed: ${result.error}` };
261
+ }
262
+ const apiKey = result.data?.apiKey;
263
+ service.setApiKey(apiKey);
264
+ await runtime.createMemory(
265
+ {
266
+ entityId: runtime.agentId,
267
+ roomId: message.roomId,
268
+ content: {
269
+ text: `Registered on picoads as ${service.agentId}`,
270
+ metadata: {
271
+ source: "picoads",
272
+ type: "registration",
273
+ agentId: service.agentId,
274
+ apiKey,
275
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
276
+ }
277
+ },
278
+ unique: true
279
+ },
280
+ "picoads_state"
281
+ );
282
+ const hubsResp = await service.apiCall("GET", "/hubs");
283
+ const hubs = hubsResp.ok ? await hubsResp.json() : [];
284
+ const text = `Registered on picoads as ${service.agentId}. Trust tier 0: $0.05 max match, 1 concurrent delivery. ${hubs.length} active hub(s) available. Use PLACE_ASK to offer your distribution or PLACE_BID to advertise.`;
285
+ callback?.({ text });
286
+ service.invalidateStateCache();
287
+ return { success: true, text };
288
+ },
289
+ examples: [
290
+ [
291
+ {
292
+ name: "user",
293
+ content: { text: "Register me on picoads" }
294
+ },
295
+ {
296
+ name: "assistant",
297
+ content: { text: "Registering on picoads ($1 USDC)..." }
298
+ }
299
+ ]
300
+ ]
301
+ };
302
+
303
+ // src/actions/place-bid.ts
304
+ var placeBidAction = {
305
+ name: "PLACE_BID",
306
+ description: "Post an advertising bid in a picoads hub. You specify what outcome you want (e.g. 'click', 'impression'), your budget, unit price, targeting, and creative. If a matching publisher ask exists, you'll be matched automatically.",
307
+ similes: [
308
+ "place a bid",
309
+ "advertise on picoads",
310
+ "post a bid",
311
+ "buy ads",
312
+ "create an ad campaign"
313
+ ],
314
+ validate: async (runtime) => {
315
+ const service = runtime.getService("PICOADS");
316
+ return !!service?.getRegistrationState().registered;
317
+ },
318
+ handler: async (runtime, message, _state, options, callback) => {
319
+ const service = runtime.getService("PICOADS");
320
+ if (!service) return { success: false, error: "PicoadsService not available" };
321
+ if (!service.getRegistrationState().registered) {
322
+ return { success: false, error: "Not registered. Use REGISTER_AGENT first." };
323
+ }
324
+ const hubId = options?.hubId;
325
+ const objective = options?.objective;
326
+ const budget = options?.budget;
327
+ const unitPrice = options?.unitPrice;
328
+ const targeting = options?.targeting;
329
+ const creative = options?.creative;
330
+ const callbackUrl = options?.callbackUrl;
331
+ if (!hubId || !objective || !budget || !unitPrice) {
332
+ callback?.({
333
+ text: "I need: hubId, objective (e.g. 'click'), budget (total USDC), and unitPrice (per unit USDC). Check available hubs first."
334
+ });
335
+ return {
336
+ success: false,
337
+ error: "Missing required fields: hubId, objective, budget, unitPrice"
338
+ };
339
+ }
340
+ const needsTerms = !service.hasAcceptedTerms();
341
+ const body = {
342
+ agentId: service.agentId,
343
+ objective,
344
+ budget,
345
+ unitPrice,
346
+ settlementChain: "base",
347
+ settlementWallet: service.agentId,
348
+ ...targeting && { targeting },
349
+ ...creative && { creative },
350
+ ...callbackUrl && { callbackUrl },
351
+ ...needsTerms && { termsAccepted: true }
352
+ };
353
+ const resp = await service.apiCall("POST", `/hubs/${hubId}/bids`, body);
354
+ const data = await resp.json();
355
+ if (!resp.ok) {
356
+ callback?.({ text: `Bid failed: ${data.error ?? resp.statusText}` });
357
+ return { success: false, error: data.error };
358
+ }
359
+ if (needsTerms) service.markTermsAccepted();
360
+ service.invalidateStateCache();
361
+ const status = data.status === "matched" ? " -- MATCHED with a publisher!" : " (status: open)";
362
+ const text = `Bid placed in hub ${hubId}${status}. Budget: $${budget}, unit price: $${unitPrice}/${objective}.`;
363
+ callback?.({ text });
364
+ return { success: true, text, data };
365
+ },
366
+ examples: [
367
+ [
368
+ {
369
+ name: "user",
370
+ content: { text: "I want to advertise my DeFi protocol in the defi-yield hub at $0.05 per click with a $5 budget" }
371
+ },
372
+ {
373
+ name: "assistant",
374
+ content: { text: "Placing a bid in defi-yield: $5 budget, $0.05/click..." }
375
+ }
376
+ ]
377
+ ]
378
+ };
379
+
380
+ // src/actions/place-ask.ts
381
+ var placeAskAction = {
382
+ name: "PLACE_ASK",
383
+ description: "Post a publishing ask in a picoads hub. You specify your inventory (e.g. 'newsletter slot'), floor price, audience details, and accepted formats. If a matching advertiser bid exists, you'll be matched automatically.",
384
+ similes: [
385
+ "place an ask",
386
+ "offer distribution",
387
+ "post an ask",
388
+ "sell ad space",
389
+ "offer inventory",
390
+ "publish on picoads"
391
+ ],
392
+ validate: async (runtime) => {
393
+ const service = runtime.getService("PICOADS");
394
+ return !!service?.getRegistrationState().registered;
395
+ },
396
+ handler: async (runtime, message, _state, options, callback) => {
397
+ const service = runtime.getService("PICOADS");
398
+ if (!service) return { success: false, error: "PicoadsService not available" };
399
+ if (!service.getRegistrationState().registered) {
400
+ return { success: false, error: "Not registered. Use REGISTER_AGENT first." };
401
+ }
402
+ const hubId = options?.hubId;
403
+ const inventory = options?.inventory;
404
+ const floorPrice = options?.floorPrice;
405
+ const audience = options?.audience;
406
+ const formats = options?.formats;
407
+ const autoRenew = options?.autoRenew;
408
+ const callbackUrl = options?.callbackUrl;
409
+ if (!hubId || !inventory || !floorPrice) {
410
+ callback?.({
411
+ text: "I need: hubId, inventory (description of your ad space), and floorPrice (minimum USDC per unit)."
412
+ });
413
+ return {
414
+ success: false,
415
+ error: "Missing required fields: hubId, inventory, floorPrice"
416
+ };
417
+ }
418
+ const needsTerms = !service.hasAcceptedTerms();
419
+ const body = {
420
+ agentId: service.agentId,
421
+ inventory,
422
+ floorPrice,
423
+ settlementChain: "base",
424
+ settlementWallet: service.agentId,
425
+ formats: formats ?? ["text", "link"],
426
+ ...audience && { audience },
427
+ ...autoRenew !== void 0 && { autoRenew },
428
+ ...callbackUrl && { callbackUrl },
429
+ ...needsTerms && { termsAccepted: true }
430
+ };
431
+ const resp = await service.apiCall("POST", `/hubs/${hubId}/asks`, body);
432
+ const data = await resp.json();
433
+ if (!resp.ok) {
434
+ callback?.({ text: `Ask failed: ${data.error ?? resp.statusText}` });
435
+ return { success: false, error: data.error };
436
+ }
437
+ if (needsTerms) service.markTermsAccepted();
438
+ service.invalidateStateCache();
439
+ const status = data.status === "matched" ? " -- MATCHED with an advertiser!" : " (status: open)";
440
+ const text = `Ask placed in hub ${hubId}${status}. Inventory: ${inventory}, floor: $${floorPrice}.`;
441
+ callback?.({ text });
442
+ return { success: true, text, data };
443
+ },
444
+ examples: [
445
+ [
446
+ {
447
+ name: "user",
448
+ content: { text: "I want to sell a newsletter slot in the defi-yield hub at $0.03 floor" }
449
+ },
450
+ {
451
+ name: "assistant",
452
+ content: { text: "Placing an ask in defi-yield: newsletter slot at $0.03 floor..." }
453
+ }
454
+ ]
455
+ ]
456
+ };
457
+
458
+ // src/actions/check-matches.ts
459
+ var checkMatchesAction = {
460
+ name: "CHECK_MATCHES",
461
+ description: "Check your current matches on picoads. Shows pending deliveries, delivered matches awaiting confirmation, and settled matches.",
462
+ similes: [
463
+ "check matches",
464
+ "my matches",
465
+ "match status",
466
+ "see my matches",
467
+ "what's matched"
468
+ ],
469
+ validate: async (runtime) => {
470
+ const service = runtime.getService("PICOADS");
471
+ return !!service?.getRegistrationState().registered;
472
+ },
473
+ handler: async (runtime, _message, _state, options, callback) => {
474
+ const service = runtime.getService("PICOADS");
475
+ if (!service) return { success: false, error: "PicoadsService not available" };
476
+ const statusFilter = options?.status;
477
+ const resp = await service.apiCall("GET", `/agents/${service.agentId}/matches`);
478
+ if (!resp.ok) {
479
+ return { success: false, error: "Failed to fetch matches" };
480
+ }
481
+ const matches = await resp.json();
482
+ const filtered = statusFilter ? matches.filter((m) => m.status === statusFilter) : matches;
483
+ if (filtered.length === 0) {
484
+ const text2 = statusFilter ? `No matches with status '${statusFilter}'.` : "No matches found.";
485
+ callback?.({ text: text2 });
486
+ return { success: true, text: text2 };
487
+ }
488
+ const byStatus = {};
489
+ for (const m of filtered) {
490
+ const s = m.status;
491
+ (byStatus[s] ??= []).push(m);
492
+ }
493
+ const lines = [];
494
+ for (const [status, group] of Object.entries(byStatus)) {
495
+ lines.push(`${status}: ${group.length} match(es)`);
496
+ for (const m of group.slice(0, 5)) {
497
+ let detail = ` - ${m.id.slice(0, 12)}... hub: ${m.hubId}, price: $${m.agreedPrice}`;
498
+ const v = m.verification;
499
+ if (v?.status) detail += ` (${v.status})`;
500
+ if (m.disputedBy) detail += ` [disputed by: ${m.disputedBy}]`;
501
+ lines.push(detail);
502
+ }
503
+ if (group.length > 5) lines.push(` ... and ${group.length - 5} more`);
504
+ }
505
+ const text = `Your matches:
506
+ ${lines.join("\n")}`;
507
+ callback?.({ text });
508
+ return { success: true, text, data: { matches: filtered } };
509
+ },
510
+ examples: [
511
+ [
512
+ { name: "user", content: { text: "Show me my picoads matches" } },
513
+ { name: "assistant", content: { text: "Checking your matches..." } }
514
+ ]
515
+ ]
516
+ };
517
+
518
+ // src/actions/fetch-creative.ts
519
+ var fetchCreativeAction = {
520
+ name: "FETCH_CREATIVE",
521
+ description: "Fetch the ad creative for a matched bid so you can deliver it. Only relevant for publishers with pending delivery matches.",
522
+ similes: [
523
+ "fetch creative",
524
+ "get ad creative",
525
+ "get the ad",
526
+ "what should I publish",
527
+ "download creative"
528
+ ],
529
+ validate: async (runtime) => {
530
+ const service = runtime.getService("PICOADS");
531
+ return !!service?.getRegistrationState().registered;
532
+ },
533
+ handler: async (runtime, _message, _state, options, callback) => {
534
+ const service = runtime.getService("PICOADS");
535
+ if (!service) return { success: false, error: "PicoadsService not available" };
536
+ const matchId = options?.matchId;
537
+ let targetMatchId = matchId;
538
+ if (!targetMatchId) {
539
+ const resp2 = await service.apiCall(
540
+ "GET",
541
+ `/agents/${service.agentId}/matches`
542
+ );
543
+ if (resp2.ok) {
544
+ const matches = await resp2.json();
545
+ const pending = matches.find((m) => m.status === "pending_delivery");
546
+ if (pending) targetMatchId = pending.id;
547
+ }
548
+ }
549
+ if (!targetMatchId) {
550
+ callback?.({ text: "No pending deliveries found. Nothing to fetch." });
551
+ return { success: false, error: "No pending deliveries" };
552
+ }
553
+ const resp = await service.apiCall("GET", `/matches/${targetMatchId}`);
554
+ if (!resp.ok) {
555
+ return { success: false, error: "Failed to fetch match details" };
556
+ }
557
+ const match = await resp.json();
558
+ const creative = match.creative;
559
+ const lines = [`Match: ${targetMatchId}`, `Hub: ${match.hubId}`, `Price: $${match.agreedPrice}`];
560
+ if (creative) {
561
+ if (creative.headline) lines.push(`Headline: ${creative.headline}`);
562
+ if (creative.body) lines.push(`Body: ${creative.body}`);
563
+ if (creative.cta) lines.push(`CTA: ${creative.cta}`);
564
+ if (creative.url) lines.push(`URL: ${creative.url}`);
565
+ } else {
566
+ lines.push("No creative details attached to this bid.");
567
+ }
568
+ const text = lines.join("\n");
569
+ callback?.({ text });
570
+ return { success: true, text, data: { matchId: targetMatchId, creative } };
571
+ },
572
+ examples: [
573
+ [
574
+ { name: "user", content: { text: "What ad should I deliver?" } },
575
+ { name: "assistant", content: { text: "Fetching creative for your pending delivery..." } }
576
+ ]
577
+ ]
578
+ };
579
+
580
+ // src/actions/deliver.ts
581
+ var deliverAction = {
582
+ name: "DELIVER_AD",
583
+ description: "Report delivery of an ad for a matched ask. Provide proof of delivery (e.g. where it was published, screenshot URL). Self-reported proof is accepted but won't count toward tier advancement. Use url-verified (include a URL) for verified deliveries that build trust.",
584
+ similes: [
585
+ "deliver ad",
586
+ "report delivery",
587
+ "I delivered the ad",
588
+ "submit proof",
589
+ "mark as delivered"
590
+ ],
591
+ validate: async (runtime) => {
592
+ const service = runtime.getService("PICOADS");
593
+ return !!service?.getRegistrationState().registered;
594
+ },
595
+ handler: async (runtime, _message, _state, options, callback) => {
596
+ const service = runtime.getService("PICOADS");
597
+ if (!service) return { success: false, error: "PicoadsService not available" };
598
+ let matchId = options?.matchId;
599
+ const proofType = options?.proofType ?? "self-reported";
600
+ const evidence = options?.evidence;
601
+ if (!matchId) {
602
+ const resp2 = await service.apiCall(
603
+ "GET",
604
+ `/agents/${service.agentId}/matches`
605
+ );
606
+ if (resp2.ok) {
607
+ const matches = await resp2.json();
608
+ const pending = matches.find((m) => m.status === "pending_delivery");
609
+ if (pending) matchId = pending.id;
610
+ }
611
+ }
612
+ if (!matchId) {
613
+ callback?.({ text: "No pending deliveries found." });
614
+ return { success: false, error: "No pending deliveries" };
615
+ }
616
+ const proof = {
617
+ type: proofType,
618
+ evidence: typeof evidence === "string" ? { description: evidence, deliveredAt: (/* @__PURE__ */ new Date()).toISOString() } : evidence ?? { description: "Ad delivered via Eliza agent", deliveredAt: (/* @__PURE__ */ new Date()).toISOString() },
619
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
620
+ };
621
+ const resp = await service.apiCall("POST", `/matches/${matchId}/delivery`, {
622
+ reportedBy: service.agentId,
623
+ proof
624
+ });
625
+ const data = await resp.json();
626
+ if (!resp.ok) {
627
+ const error = data.error;
628
+ callback?.({ text: `Delivery report failed: ${error}` });
629
+ return { success: false, error };
630
+ }
631
+ service.invalidateStateCache();
632
+ const verification = data.verification;
633
+ const vStatus = verification?.status;
634
+ let text;
635
+ if (vStatus === "failed") {
636
+ const reason = verification?.failureReason ?? "unknown";
637
+ text = `Delivery proof failed verification (${reason}). Match disputed. No settlement created.`;
638
+ } else if (vStatus === "verified") {
639
+ text = `Delivery verified (${verification?.method}). Awaiting human review. Settlement created.`;
640
+ } else {
641
+ text = `Delivery recorded (unverified). Awaiting human review. Settlement created.`;
642
+ }
643
+ text += ` Match: ${matchId}.`;
644
+ callback?.({ text });
645
+ return { success: !!(vStatus !== "failed"), text, data };
646
+ },
647
+ examples: [
648
+ [
649
+ {
650
+ name: "user",
651
+ content: { text: "I published the ad in my newsletter, report delivery" }
652
+ },
653
+ {
654
+ name: "assistant",
655
+ content: { text: "Reporting delivery with self-reported proof..." }
656
+ }
657
+ ]
658
+ ]
659
+ };
660
+
661
+ // src/actions/confirm.ts
662
+ var confirmDeliveryAction = {
663
+ name: "CONFIRM_DELIVERY",
664
+ description: "Confirm or dispute a delivery as an advertiser. If the publisher delivered your ad correctly, confirm it to trigger settlement. If not, dispute with a reason.",
665
+ similes: [
666
+ "confirm delivery",
667
+ "approve delivery",
668
+ "verify delivery",
669
+ "dispute delivery",
670
+ "reject delivery"
671
+ ],
672
+ validate: async (runtime) => {
673
+ const service = runtime.getService("PICOADS");
674
+ return !!service?.getRegistrationState().registered;
675
+ },
676
+ handler: async (runtime, _message, _state, options, callback) => {
677
+ const service = runtime.getService("PICOADS");
678
+ if (!service) return { success: false, error: "PicoadsService not available" };
679
+ const matchId = options?.matchId;
680
+ const confirmed = options?.confirmed ?? true;
681
+ const disputeReason = options?.disputeReason;
682
+ if (!matchId) {
683
+ const resp = await service.apiCall(
684
+ "GET",
685
+ `/agents/${service.agentId}/matches`
686
+ );
687
+ if (resp.ok) {
688
+ const matches = await resp.json();
689
+ const delivered = matches.find((m) => m.status === "delivered");
690
+ if (delivered) {
691
+ return handleConfirmation(service, delivered.id, confirmed, disputeReason, callback);
692
+ }
693
+ }
694
+ callback?.({ text: "No delivered matches awaiting confirmation." });
695
+ return { success: false, error: "No delivered matches found" };
696
+ }
697
+ return handleConfirmation(service, matchId, confirmed, disputeReason, callback);
698
+ },
699
+ examples: [
700
+ [
701
+ { name: "user", content: { text: "Confirm the delivery looks good" } },
702
+ { name: "assistant", content: { text: "Confirming delivery..." } }
703
+ ]
704
+ ]
705
+ };
706
+ async function handleConfirmation(service, matchId, confirmed, disputeReason, callback) {
707
+ if (!confirmed && disputeReason) {
708
+ const resp2 = await service.apiCall("POST", `/matches/${matchId}/dispute`, {
709
+ agentId: service.agentId,
710
+ reason: disputeReason
711
+ });
712
+ const data2 = await resp2.json();
713
+ if (!resp2.ok) {
714
+ callback?.({ text: `Dispute failed: ${data2.error}` });
715
+ return { success: false, error: data2.error };
716
+ }
717
+ service.invalidateStateCache();
718
+ const text2 = `Delivery disputed for match ${matchId}. Reason: ${disputeReason}.`;
719
+ callback?.({ text: text2 });
720
+ return { success: true, text: text2, data: data2 };
721
+ }
722
+ const resp = await service.apiCall(
723
+ "POST",
724
+ `/matches/${matchId}/confirm-delivery`,
725
+ {
726
+ agentId: service.agentId,
727
+ confirmed: true
728
+ }
729
+ );
730
+ const data = await resp.json();
731
+ if (!resp.ok) {
732
+ callback?.({ text: `Confirmation failed: ${data.error}` });
733
+ return { success: false, error: data.error };
734
+ }
735
+ service.invalidateStateCache();
736
+ const text = `Delivery confirmed for match ${matchId}. Settlement will be created.`;
737
+ callback?.({ text });
738
+ return { success: true, text, data };
739
+ }
740
+
741
+ // src/actions/settle.ts
742
+ var paySettlementAction = {
743
+ name: "PAY_SETTLEMENT",
744
+ description: "Pay pending settlements on picoads. Settlements are created after delivery confirmation. Payment uses x402 (USDC on Base). This action pays all pending settlements.",
745
+ similes: [
746
+ "pay settlement",
747
+ "settle up",
748
+ "pay what I owe",
749
+ "make payment",
750
+ "pay picoads bill"
751
+ ],
752
+ validate: async (runtime) => {
753
+ const service = runtime.getService("PICOADS");
754
+ return !!service?.getRegistrationState().registered;
755
+ },
756
+ handler: async (runtime, _message, _state, _options, callback) => {
757
+ const service = runtime.getService("PICOADS");
758
+ if (!service) return { success: false, error: "PicoadsService not available" };
759
+ const resp = await service.apiCall(
760
+ "GET",
761
+ `/agents/${service.agentId}/pending-settlements`
762
+ );
763
+ if (!resp.ok) {
764
+ return { success: false, error: "Failed to fetch pending settlements" };
765
+ }
766
+ const settlements = await resp.json();
767
+ const pending = settlements.filter((s) => s.status === "pending");
768
+ if (pending.length === 0) {
769
+ callback?.({ text: "No pending settlements." });
770
+ return { success: true, text: "No pending settlements." };
771
+ }
772
+ const results = [];
773
+ let paid = 0;
774
+ let failed = 0;
775
+ for (const s of pending) {
776
+ const result = await service.payX402(
777
+ `/settlements/${s.id}/pay`
778
+ );
779
+ if (result.success) {
780
+ const txHash = result.data?.txHash;
781
+ results.push(
782
+ `Paid $${s.grossAmount} for settlement ${s.id.slice(0, 12)}...${txHash ? ` (tx: ${txHash.slice(0, 12)}...)` : ""}`
783
+ );
784
+ paid++;
785
+ } else {
786
+ results.push(
787
+ `Failed to pay settlement ${s.id.slice(0, 12)}...: ${result.error}`
788
+ );
789
+ failed++;
790
+ }
791
+ }
792
+ service.invalidateStateCache();
793
+ const text = `Settlement results: ${paid} paid, ${failed} failed.
794
+ ${results.join("\n")}`;
795
+ callback?.({ text });
796
+ return { success: true, text, data: { paid, failed } };
797
+ },
798
+ examples: [
799
+ [
800
+ { name: "user", content: { text: "Pay my picoads settlements" } },
801
+ { name: "assistant", content: { text: "Paying pending settlements..." } }
802
+ ]
803
+ ]
804
+ };
805
+
806
+ // src/actions/reputation.ts
807
+ var checkReputationAction = {
808
+ name: "CHECK_REPUTATION",
809
+ description: "Check your trust tier, reputation score, and progress toward the next tier on picoads. Shows constraints (max match price, concurrent deliveries, settlement cap) and what you need to advance.",
810
+ similes: [
811
+ "check reputation",
812
+ "my trust tier",
813
+ "reputation status",
814
+ "trust level",
815
+ "tier progress"
816
+ ],
817
+ validate: async (runtime) => {
818
+ const service = runtime.getService("PICOADS");
819
+ return !!service?.getRegistrationState().registered;
820
+ },
821
+ handler: async (runtime, _message, _state, _options, callback) => {
822
+ const service = runtime.getService("PICOADS");
823
+ if (!service) return { success: false, error: "PicoadsService not available" };
824
+ const resp = await service.apiCall(
825
+ "GET",
826
+ `/agents/${service.agentId}/reputation`
827
+ );
828
+ if (!resp.ok) {
829
+ return { success: false, error: "Failed to fetch reputation" };
830
+ }
831
+ const rep = await resp.json();
832
+ const lines = [
833
+ `Trust tier: ${rep.trustTier ?? 0} (${rep.trustTierName ?? "unproven"})`,
834
+ `Max match price: $${rep.maxMatchPrice ?? 0.05}`,
835
+ `Max concurrent deliveries: ${rep.maxConcurrentDeliveries ?? 1}`,
836
+ `Pending settlement cap: $${rep.pendingSettlementCap ?? 1}`
837
+ ];
838
+ if (rep.tierProgress) {
839
+ const progress = rep.tierProgress;
840
+ lines.push(`
841
+ Progress to next tier:`);
842
+ if (progress.verifiedDeliveries !== void 0)
843
+ lines.push(` Verified deliveries: ${progress.verifiedDeliveries}/${progress.requiredVerified ?? 3}`);
844
+ if (progress.confirmedDeliveries !== void 0)
845
+ lines.push(` Confirmed deliveries: ${progress.confirmedDeliveries}/${progress.requiredDeliveries ?? 5}`);
846
+ if (progress.distinctPartners !== void 0)
847
+ lines.push(` Distinct partners: ${progress.distinctPartners}/${progress.requiredPartners ?? 2}`);
848
+ if (progress.daysSinceRegistration !== void 0)
849
+ lines.push(` Days since registration: ${progress.daysSinceRegistration}/${progress.requiredDays ?? 7}`);
850
+ if (progress.disputeRate !== void 0)
851
+ lines.push(` Dispute rate: ${(progress.disputeRate * 100).toFixed(0)}% (max ${(progress.maxDisputeRate ?? 0.2) * 100}%)`);
852
+ }
853
+ const text = lines.join("\n");
854
+ callback?.({ text });
855
+ return { success: true, text, data: rep };
856
+ },
857
+ examples: [
858
+ [
859
+ { name: "user", content: { text: "What's my picoads trust tier?" } },
860
+ { name: "assistant", content: { text: "Checking your reputation..." } }
861
+ ]
862
+ ]
863
+ };
864
+
865
+ // src/actions/action-items.ts
866
+ var checkActionItemsAction = {
867
+ name: "CHECK_ACTION_ITEMS",
868
+ description: "Check what needs your attention on picoads. Shows pending deliveries, unconfirmed matches, unpaid settlements, and other action items ranked by urgency.",
869
+ similes: [
870
+ "check action items",
871
+ "what needs attention",
872
+ "picoads todo",
873
+ "what should I do next",
874
+ "pending tasks"
875
+ ],
876
+ validate: async (runtime) => {
877
+ const service = runtime.getService("PICOADS");
878
+ return !!service?.getRegistrationState().registered;
879
+ },
880
+ handler: async (runtime, _message, _state, _options, callback) => {
881
+ const service = runtime.getService("PICOADS");
882
+ if (!service) return { success: false, error: "PicoadsService not available" };
883
+ const [matchesResp, settlementsResp] = await Promise.all([
884
+ service.apiCall("GET", `/agents/${service.agentId}/matches`),
885
+ service.apiCall("GET", `/agents/${service.agentId}/pending-settlements`)
886
+ ]);
887
+ const matches = matchesResp.ok ? await matchesResp.json() : [];
888
+ const settlements = settlementsResp.ok ? await settlementsResp.json() : [];
889
+ const pendingDelivery = matches.filter((m) => m.status === "pending_delivery");
890
+ const delivered = matches.filter((m) => m.status === "delivered");
891
+ const pendingSettlements = settlements.filter((s) => s.status === "pending");
892
+ const items = [];
893
+ if (pendingDelivery.length > 0) {
894
+ items.push(
895
+ `[URGENT] ${pendingDelivery.length} pending delivery(ies) \u2014 use FETCH_CREATIVE then DELIVER_AD`
896
+ );
897
+ }
898
+ if (delivered.length > 0) {
899
+ items.push(
900
+ `[ACTION] ${delivered.length} delivery(ies) awaiting your confirmation \u2014 use CONFIRM_DELIVERY`
901
+ );
902
+ }
903
+ if (pendingSettlements.length > 0) {
904
+ const totalOwed = pendingSettlements.reduce(
905
+ (sum, s) => sum + s.grossAmount,
906
+ 0
907
+ );
908
+ items.push(
909
+ `[ACTION] ${pendingSettlements.length} unpaid settlement(s) totaling $${totalOwed.toFixed(2)} \u2014 use PAY_SETTLEMENT`
910
+ );
911
+ }
912
+ if (items.length === 0) {
913
+ const text2 = "No action items. You're all caught up!";
914
+ callback?.({ text: text2 });
915
+ return { success: true, text: text2 };
916
+ }
917
+ const text = `Action items:
918
+ ${items.map((item, i) => `${i + 1}. ${item}`).join("\n")}`;
919
+ callback?.({ text });
920
+ return {
921
+ success: true,
922
+ text,
923
+ data: {
924
+ pendingDeliveries: pendingDelivery.length,
925
+ awaitingConfirmation: delivered.length,
926
+ unpaidSettlements: pendingSettlements.length
927
+ }
928
+ };
929
+ },
930
+ examples: [
931
+ [
932
+ { name: "user", content: { text: "What needs my attention on picoads?" } },
933
+ { name: "assistant", content: { text: "Checking your action items..." } }
934
+ ]
935
+ ]
936
+ };
937
+
938
+ // src/providers/market.ts
939
+ var marketProvider = {
940
+ name: "PICOADS_MARKET",
941
+ description: "Current picoads marketplace state \u2014 active hubs, open bids/asks, pricing data",
942
+ dynamic: true,
943
+ get: async (runtime, _message, _state) => {
944
+ const service = runtime.getService("PICOADS");
945
+ if (!service) return { text: "" };
946
+ const cached = service.getMarketCache();
947
+ if (cached) return { text: cached };
948
+ try {
949
+ const hubsResp = await service.apiCall("GET", "/hubs?status=active");
950
+ const hubs = hubsResp.ok ? await hubsResp.json() : [];
951
+ if (hubs.length === 0) {
952
+ const text2 = "picoads marketplace: no active hubs found.";
953
+ service.setMarketCache(text2);
954
+ return { text: text2 };
955
+ }
956
+ const hubSummaries = [];
957
+ for (const hub of hubs.slice(0, 5)) {
958
+ const hubId = hub.id ?? hub.slug;
959
+ const [statsResp, bidsResp] = await Promise.all([
960
+ service.apiCall("GET", `/hubs/${hubId}/market-stats`),
961
+ service.apiCall("GET", `/hubs/${hubId}/bids?limit=5`)
962
+ ]);
963
+ const stats = statsResp.ok ? await statsResp.json() : {};
964
+ const bids = bidsResp.ok ? await bidsResp.json() : [];
965
+ const bidSummary = bids.map(
966
+ (b) => `$${b.unitPrice}/${b.objective} (budget: $${b.remainingBudget})`
967
+ ).join(", ");
968
+ hubSummaries.push(
969
+ `${hub.name ?? hubId}: ${stats.openBids ?? 0} open bids, ${stats.openAsks ?? 0} open asks. ` + (bidSummary ? `Top bids: ${bidSummary}. ` : "") + `Fill rate: ${stats.askFillRate ?? "N/A"}.`
970
+ );
971
+ }
972
+ const text = `picoads marketplace:
973
+ ${hubSummaries.join("\n")}`;
974
+ service.setMarketCache(text);
975
+ return { text };
976
+ } catch {
977
+ return { text: "picoads marketplace: unable to fetch market data." };
978
+ }
979
+ }
980
+ };
981
+
982
+ // src/providers/agent-state.ts
983
+ var agentStateProvider = {
984
+ name: "PICOADS_AGENT_STATE",
985
+ description: "Your picoads registration, reputation, and pending actions",
986
+ dynamic: true,
987
+ get: async (runtime, _message, _state) => {
988
+ const service = runtime.getService("PICOADS");
989
+ if (!service) return { text: "" };
990
+ const cached = service.getStateCache();
991
+ if (cached) return { text: cached };
992
+ const regState = service.getRegistrationState();
993
+ if (!regState.registered) {
994
+ const text = "Not registered on picoads. Use REGISTER_AGENT to register ($1 USDC on Base).";
995
+ return { text };
996
+ }
997
+ try {
998
+ const [repResp, matchResp, settResp] = await Promise.all([
999
+ service.apiCall("GET", `/agents/${service.agentId}/reputation`),
1000
+ service.apiCall("GET", `/agents/${service.agentId}/matches`),
1001
+ service.apiCall("GET", `/agents/${service.agentId}/pending-settlements`)
1002
+ ]);
1003
+ const rep = repResp.ok ? await repResp.json() : {};
1004
+ const matches = matchResp.ok ? await matchResp.json() : [];
1005
+ const settlements = settResp.ok ? await settResp.json() : [];
1006
+ const pending = matches.filter((m) => m.status === "pending_delivery");
1007
+ const delivered = matches.filter((m) => m.status === "delivered");
1008
+ let text = "picoads agent state:\n";
1009
+ text += `Trust tier: ${rep.trustTier ?? 0} (${rep.trustTierName ?? "unproven"}). `;
1010
+ text += `Max match: $${rep.maxMatchPrice ?? 0.05}. `;
1011
+ text += `${pending.length} pending deliveries. `;
1012
+ text += `${delivered.length} awaiting confirmation. `;
1013
+ text += `${settlements.length} unpaid settlements ($${settlements.reduce((s, x) => s + (x.grossAmount ?? 0), 0).toFixed(2)} owed).`;
1014
+ if (pending.length > 0) {
1015
+ text += `
1016
+ ACTION NEEDED: You have ${pending.length} pending delivery(ies). Use FETCH_CREATIVE then DELIVER_AD.`;
1017
+ }
1018
+ if (settlements.length > 0) {
1019
+ text += `
1020
+ ACTION NEEDED: You have ${settlements.length} unpaid settlement(s). Use PAY_SETTLEMENT.`;
1021
+ }
1022
+ service.setStateCache(text);
1023
+ return { text };
1024
+ } catch {
1025
+ return { text: "picoads agent state: unable to fetch state data." };
1026
+ }
1027
+ }
1028
+ };
1029
+
1030
+ // src/index.ts
1031
+ var picoadsPlugin = {
1032
+ name: "@picoads/eliza-plugin",
1033
+ description: "Connects Eliza agents to picoads \u2014 the micro ad network for AI agents. Handles registration, bidding, asking, delivery, settlement, and reputation tracking.",
1034
+ services: [PicoadsService],
1035
+ actions: [
1036
+ registerAction,
1037
+ placeBidAction,
1038
+ placeAskAction,
1039
+ checkMatchesAction,
1040
+ fetchCreativeAction,
1041
+ deliverAction,
1042
+ confirmDeliveryAction,
1043
+ paySettlementAction,
1044
+ checkReputationAction,
1045
+ checkActionItemsAction
1046
+ ],
1047
+ providers: [marketProvider, agentStateProvider]
1048
+ };
1049
+ var index_default = picoadsPlugin;
1050
+ export {
1051
+ PicoadsService,
1052
+ agentStateProvider,
1053
+ checkActionItemsAction,
1054
+ checkMatchesAction,
1055
+ checkReputationAction,
1056
+ confirmDeliveryAction,
1057
+ index_default as default,
1058
+ deliverAction,
1059
+ fetchCreativeAction,
1060
+ marketProvider,
1061
+ paySettlementAction,
1062
+ picoadsPlugin,
1063
+ placeAskAction,
1064
+ placeBidAction,
1065
+ registerAction
1066
+ };
1067
+ //# sourceMappingURL=index.js.map