@panal/sdk 0.17.1 → 0.18.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/README.md CHANGED
@@ -71,7 +71,17 @@ const panal = createPanalClient({
71
71
 
72
72
  **Escritura** — `hire({ agent, brief, amount?, deadline? })` · `approveTask(id, rating)` · `withdraw(currency?)`
73
73
 
74
- **Utilidades** — `parseAgentMetadata()` · `formatAgentMetadata()` · `leerTipo()` · `leerNivelesDeMetadata()` / `nivelPara()` · `rutaDeAgente()` · `fichaEnIdioma()` · `MAINNET_ADDRESSES` · `NATIVE_CURRENCY` · `TaskStatus` · los ABIs
74
+ **El tablón** — `listBoard()` · `claimTask(id)` · `readBoardBrief(id)` · `deliverBoardResult(id, texto)`
75
+
76
+ Encargos pagados **sin elegir agente**, para que los coja un programa. En ese orden:
77
+ mirar, coger, leer y entregar. `listBoard` no se cree al buzón —comprueba la firma de
78
+ cada anuncio y cada tarea contra la cadena—, `claimTask` explica por qué no se puede
79
+ coger antes de gastar gas, `readBoardBrief` rechaza un texto que no cuadre con el
80
+ `taskHash`, y `deliverBoardResult` deja la entrega en el buzón **antes** de anclarla:
81
+ al revés, un fallo del buzón dejaría al cliente con una entrega que no puede descargar.
82
+ Coger trabajo exige que la cuenta sea un agente registrado y activo.
83
+
84
+ **Utilidades** — `encargoSignMessage()` / `entregaSignMessage()` / `ofertaSignMessage()` · `TABLON` · `parseAgentMetadata()` · `formatAgentMetadata()` · `leerTipo()` · `leerNivelesDeMetadata()` / `nivelPara()` · `rutaDeAgente()` · `fichaEnIdioma()` · `MAINNET_ADDRESSES` · `NATIVE_CURRENCY` · `TaskStatus` · los ABIs
75
85
 
76
86
  ### El metadata de un agente
77
87
 
package/dist/abis.d.ts CHANGED
@@ -159,6 +159,15 @@ export declare const escrowAbi: readonly [{
159
159
  readonly type: "address";
160
160
  }];
161
161
  }];
162
+ }, {
163
+ readonly type: "function";
164
+ readonly name: "claimTask";
165
+ readonly stateMutability: "nonpayable";
166
+ readonly inputs: readonly [{
167
+ readonly name: "taskId";
168
+ readonly type: "uint256";
169
+ }];
170
+ readonly outputs: readonly [];
162
171
  }, {
163
172
  readonly type: "function";
164
173
  readonly name: "deliverResult";
package/dist/abis.js CHANGED
@@ -115,6 +115,15 @@ export const escrowAbi = [
115
115
  },
116
116
  ],
117
117
  },
118
+ {
119
+ // Coger una tarea del tablón (worker = address(0)). El contrato exige que
120
+ // quien la coge sea un agente activo y que no sea el propio cliente.
121
+ type: 'function',
122
+ name: 'claimTask',
123
+ stateMutability: 'nonpayable',
124
+ inputs: [{ name: 'taskId', type: 'uint256' }],
125
+ outputs: [],
126
+ },
118
127
  {
119
128
  type: 'function',
120
129
  name: 'deliverResult',
package/dist/client.d.ts CHANGED
@@ -19,6 +19,7 @@ import type { Account, Address, Hex, PublicClient, WalletClient } from 'viem';
19
19
  import { type AskResult, type X402Accept } from './x402.js';
20
20
  import { type CallEnvelope } from './envelope.js';
21
21
  import { type PanalAddresses, type PanalNetwork } from './chains.js';
22
+ import { type EncargoDelTablon } from './tablon.js';
22
23
  import { TaskStatus, type Agent, type AgentMetadata, type Task } from './types.js';
23
24
  export interface PanalClientOptions {
24
25
  /** `mainnet` por defecto. */
@@ -43,6 +44,15 @@ export interface PanalClientOptions {
43
44
  * `null` lo desactiva y lee siempre de la cadena.
44
45
  */
45
46
  indexerUrl?: string | null;
47
+ /**
48
+ * El buzón que guarda los textos del tablón. `https://api.panal.lat/buzon`
49
+ * por defecto.
50
+ *
51
+ * Va aparte del indexador a propósito: desactivar el indexador (`null`)
52
+ * obliga a leer agentes de la cadena, pero no puede dejar a un programa sin
53
+ * tablón, porque el texto de un encargo sin dueño no está en la cadena.
54
+ */
55
+ buzonUrl?: string;
46
56
  }
47
57
  export interface HireParams {
48
58
  /** Dirección del agente que hará el trabajo. */
@@ -115,6 +125,7 @@ export declare class PanalClient {
115
125
  readonly walletClient?: WalletClient;
116
126
  /** Indexador para buscar agentes, o null si se lee siempre de la cadena. */
117
127
  readonly indexerUrl: string | null;
128
+ readonly buzonUrl: string;
118
129
  constructor(options?: PanalClientOptions);
119
130
  /** El wallet client, o un error que dice exactamente qué falta. */
120
131
  private wallet;
@@ -265,6 +276,56 @@ export declare class PanalClient {
265
276
  limit?: number;
266
277
  status?: TaskStatus;
267
278
  }): Promise<Task[]>;
279
+ /**
280
+ * Los encargos del tablón que se pueden coger AHORA.
281
+ *
282
+ * Lo que sirve el buzón no se da por bueno: cada anuncio se comprueba contra
283
+ * la firma de su cliente —si no cuadra, el buzón lo ha cambiado o se lo ha
284
+ * inventado— y cada tarea contra la cadena, porque el buzón no se entera de
285
+ * que alguien la cogió, la canceló o se le pasó el plazo. Sin esto, un
286
+ * programa gastaría gas intentando coger encargos que ya no existen.
287
+ *
288
+ * `limit` acota cuántos anuncios se cruzan con la cadena, empezando por los
289
+ * más recientes: cada uno es una lectura al RPC.
290
+ */
291
+ listBoard(options?: {
292
+ limit?: number;
293
+ }): Promise<EncargoDelTablon[]>;
294
+ /**
295
+ * Coge un encargo del tablón: desde aquí eres su trabajador en la cadena.
296
+ *
297
+ * Lo que el contrato rechazaría se comprueba antes, gratis, para decir POR QUÉ
298
+ * en vez de devolver un revert que no explica nada: que ya la cogió otro, que
299
+ * es tuya, que venció, o que no eres un agente activo. Esto último es lo que
300
+ * pide `claimTask` y lo que más fácil se olvida: coger trabajo exige estar
301
+ * registrado y dado de alta.
302
+ */
303
+ claimTask(taskId: bigint): Promise<{
304
+ txHash: Hex;
305
+ }>;
306
+ /**
307
+ * Lee el encargo de una tarea del tablón que ya has cogido.
308
+ *
309
+ * Se comprueba su keccak256 contra el `taskHash` de la cadena. Ese hash es lo
310
+ * que se pagó y lo que un árbitro miraría en una disputa, así que un texto que
311
+ * no cuadre no se devuelve: trabajar sobre él sería cumplir algo que nadie
312
+ * encargó.
313
+ */
314
+ readBoardBrief(taskId: bigint): Promise<string>;
315
+ /**
316
+ * Entrega una tarea del tablón: deja el texto en el buzón y ancla su hash.
317
+ *
318
+ * EN ESE ORDEN, y no al revés. El cliente recoge la entrega del buzón, porque
319
+ * cuando publicó el encargo no sabía quién lo iba a coger ni dónde vive su
320
+ * servidor. Si se anclara primero y el buzón fallara después, el cliente
321
+ * vería una entrega en la cadena que no puede descargar. Dejándola primero,
322
+ * un fallo del buzón no ancla nada y se puede reintentar; y el buzón acepta
323
+ * repetir la misma entrega, porque da el mismo hash.
324
+ */
325
+ deliverBoardResult(taskId: bigint, resultText: string): Promise<{
326
+ txHash: Hex;
327
+ resultHash: Hex;
328
+ }>;
268
329
  /**
269
330
  * Pregunta el precio de un agente sin pagar nada.
270
331
  *
package/dist/client.js CHANGED
@@ -15,13 +15,14 @@
15
15
  * Sin configuración apunta a Monad mainnet, que es donde Panal está desplegado
16
16
  * y en uso: el caso de "quiero probar esto ahora" no debería exigir un .env.
17
17
  */
18
- import { createPublicClient, createWalletClient, formatEther, getAddress, http, keccak256, toBytes } from 'viem';
18
+ import { createPublicClient, createWalletClient, formatEther, getAddress, http, keccak256, toBytes, verifyMessage } from 'viem';
19
19
  import { erc20Abi, escrowAbi, namesAbi, registryAbi } from './abis.js';
20
20
  import { leerX402 } from './agent-card.js';
21
21
  import { assertPublicUrl, fetchLimited, rutaDeAgente } from './net.js';
22
22
  import { X402Error, payAndAsk, quoteAsk } from './x402.js';
23
23
  import { descend, newEnvelope, remainingBudget } from './envelope.js';
24
24
  import { NATIVE_CURRENCY, addressesFor, chainFor } from './chains.js';
25
+ import { BUZON_URL, TABLON, VENTANA_FIRMA_S, encargoSignMessage, entregaSignMessage, ofertaSignMessage, } from './tablon.js';
25
26
  import { TaskStatus, formatAgentMetadata, parseAgentMetadata, } from './types.js';
26
27
  /** Cuántos agentes se leen por llamada al registry. */
27
28
  const REGISTRY_PAGE = 50n;
@@ -100,6 +101,7 @@ export class PanalClient {
100
101
  walletClient;
101
102
  /** Indexador para buscar agentes, o null si se lee siempre de la cadena. */
102
103
  indexerUrl;
104
+ buzonUrl;
103
105
  constructor(options = {}) {
104
106
  this.network = options.network ?? 'mainnet';
105
107
  const chain = chainFor(this.network);
@@ -109,6 +111,7 @@ export class PanalClient {
109
111
  'Usa network: "mainnet", o pasa `addresses` con los tuyos.');
110
112
  }
111
113
  this.indexerUrl = options.indexerUrl === undefined ? 'https://api.panal.lat' : options.indexerUrl;
114
+ this.buzonUrl = (options.buzonUrl ?? BUZON_URL).replace(/\/+$/, '');
112
115
  const transport = http(options.rpcUrl ?? chain.rpcUrls.default.http[0]);
113
116
  this.publicClient = createPublicClient({ chain, transport });
114
117
  this.account = options.account;
@@ -633,6 +636,194 @@ export class PanalClient {
633
636
  return found;
634
637
  }
635
638
  // -------------------------------------------------------------------------
639
+ // El tablón: encargos sin dueño que coge el primer agente que los quiera.
640
+ //
641
+ // El orden de uso es el de estos cuatro métodos: mirar, coger, leer y
642
+ // entregar. Entre coger y leer no hay atajo posible: el encargo solo se le
643
+ // enseña a quien ya figura en la cadena como su trabajador.
644
+ // -------------------------------------------------------------------------
645
+ /**
646
+ * Los encargos del tablón que se pueden coger AHORA.
647
+ *
648
+ * Lo que sirve el buzón no se da por bueno: cada anuncio se comprueba contra
649
+ * la firma de su cliente —si no cuadra, el buzón lo ha cambiado o se lo ha
650
+ * inventado— y cada tarea contra la cadena, porque el buzón no se entera de
651
+ * que alguien la cogió, la canceló o se le pasó el plazo. Sin esto, un
652
+ * programa gastaría gas intentando coger encargos que ya no existen.
653
+ *
654
+ * `limit` acota cuántos anuncios se cruzan con la cadena, empezando por los
655
+ * más recientes: cada uno es una lectura al RPC.
656
+ */
657
+ async listBoard(options = {}) {
658
+ const limit = options.limit ?? 50;
659
+ const { status, text } = await fetchLimited(`${this.buzonUrl}/${TABLON}/lista`, {
660
+ timeoutMs: 10_000,
661
+ maxBytes: 2_000_000,
662
+ });
663
+ if (status !== 200)
664
+ throw new Error(`El buzón respondió ${status} al pedir el tablón.`);
665
+ const ofertas = (JSON.parse(text).ofertas ?? [])
666
+ .slice()
667
+ .sort((a, b) => b.publicada - a.publicada)
668
+ .slice(0, limit);
669
+ const ahora = BigInt(Math.floor(Date.now() / 1000));
670
+ const libres = [];
671
+ for (const o of ofertas) {
672
+ let taskId;
673
+ try {
674
+ taskId = BigInt(o.taskId);
675
+ }
676
+ catch {
677
+ continue;
678
+ }
679
+ const firmada = await verifyMessage({
680
+ address: getAddress(o.cliente),
681
+ message: ofertaSignMessage(taskId, o.publico),
682
+ signature: o.firma,
683
+ }).catch(() => false);
684
+ if (!firmada)
685
+ continue;
686
+ const task = await this.getTask(taskId).catch(() => null);
687
+ if (!task)
688
+ continue;
689
+ if (task.status !== TaskStatus.Open)
690
+ continue;
691
+ if (task.worker.toLowerCase() !== TABLON)
692
+ continue;
693
+ if (task.deadline <= ahora)
694
+ continue;
695
+ // El anuncio lo firmó alguien, pero la tarea es de quien la pagó: si no
696
+ // coinciden, el anuncio no es de esta tarea.
697
+ if (task.client.toLowerCase() !== o.cliente.toLowerCase())
698
+ continue;
699
+ libres.push({
700
+ taskId,
701
+ anuncio: o.publico,
702
+ cliente: task.client,
703
+ amount: task.amount,
704
+ currency: task.currency,
705
+ deadline: task.deadline,
706
+ taskHash: task.taskHash,
707
+ publicada: o.publicada,
708
+ });
709
+ }
710
+ return libres;
711
+ }
712
+ /**
713
+ * Coge un encargo del tablón: desde aquí eres su trabajador en la cadena.
714
+ *
715
+ * Lo que el contrato rechazaría se comprueba antes, gratis, para decir POR QUÉ
716
+ * en vez de devolver un revert que no explica nada: que ya la cogió otro, que
717
+ * es tuya, que venció, o que no eres un agente activo. Esto último es lo que
718
+ * pide `claimTask` y lo que más fácil se olvida: coger trabajo exige estar
719
+ * registrado y dado de alta.
720
+ */
721
+ async claimTask(taskId) {
722
+ const wallet = this.wallet();
723
+ const yo = this.account.address;
724
+ const task = await this.getTask(taskId);
725
+ if (task.status !== TaskStatus.Open) {
726
+ throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se coge lo que sigue abierto.`);
727
+ }
728
+ if (task.worker.toLowerCase() !== TABLON) {
729
+ throw new Error(`La tarea #${taskId} ya la cogió ${task.worker}.`);
730
+ }
731
+ if (task.client.toLowerCase() === yo.toLowerCase()) {
732
+ throw new Error(`La tarea #${taskId} la publicaste tú: el contrato no deja coger un encargo propio.`);
733
+ }
734
+ if (task.deadline <= BigInt(Math.floor(Date.now() / 1000))) {
735
+ throw new Error(`La tarea #${taskId} ya venció: no daría tiempo a entregarla.`);
736
+ }
737
+ const ficha = await this.leerAgente(yo);
738
+ if (!ficha.active) {
739
+ throw new Error(`${yo} no es un agente activo en el registro, y claimTask solo acepta agentes activos. ` +
740
+ 'Regístrate (o reactívate) antes de coger trabajo.');
741
+ }
742
+ const txHash = await wallet.writeContract({
743
+ address: this.addresses.escrow,
744
+ abi: escrowAbi,
745
+ functionName: 'claimTask',
746
+ args: [taskId],
747
+ chain: chainFor(this.network),
748
+ account: this.account,
749
+ });
750
+ await this.publicClient.waitForTransactionReceipt({ hash: txHash });
751
+ return { txHash };
752
+ }
753
+ /**
754
+ * Lee el encargo de una tarea del tablón que ya has cogido.
755
+ *
756
+ * Se comprueba su keccak256 contra el `taskHash` de la cadena. Ese hash es lo
757
+ * que se pagó y lo que un árbitro miraría en una disputa, así que un texto que
758
+ * no cuadre no se devuelve: trabajar sobre él sería cumplir algo que nadie
759
+ * encargó.
760
+ */
761
+ async readBoardBrief(taskId) {
762
+ const wallet = this.wallet();
763
+ const yo = this.account.address;
764
+ const task = await this.getTask(taskId);
765
+ if (task.worker.toLowerCase() !== yo.toLowerCase()) {
766
+ throw new Error(task.worker.toLowerCase() === TABLON
767
+ ? `La tarea #${taskId} todavía no la has cogido: llama antes a claimTask.`
768
+ : `La tarea #${taskId} la cogió ${task.worker}, no tú.`);
769
+ }
770
+ const expira = Math.floor(Date.now() / 1000) + VENTANA_FIRMA_S;
771
+ const firma = await wallet.signMessage({ account: this.account, message: encargoSignMessage(taskId, expira) });
772
+ const { status, text } = await fetchLimited(`${this.buzonUrl}/${TABLON}/encargo/${taskId}`, {
773
+ headers: { 'x-panal-address': yo, 'x-panal-signature': firma, 'x-panal-expira': String(expira) },
774
+ timeoutMs: 15_000,
775
+ maxBytes: 1_000_000,
776
+ });
777
+ if (status === 404) {
778
+ throw new Error(`El buzón no tiene el encargo de la tarea #${taskId} (hash ${task.taskHash}): ` +
779
+ 'su cliente pagó pero no llegó a dejar el texto.');
780
+ }
781
+ if (status !== 200)
782
+ throw new Error(`El buzón respondió ${status} al pedir el encargo #${taskId}.`);
783
+ const { brief } = JSON.parse(text);
784
+ if (typeof brief !== 'string')
785
+ throw new Error(`El buzón devolvió el encargo #${taskId} sin texto.`);
786
+ if (keccak256(toBytes(brief)).toLowerCase() !== task.taskHash.toLowerCase()) {
787
+ throw new Error(`El encargo #${taskId} que sirve el buzón no cuadra con el taskHash de la cadena: no se trabaja sobre él.`);
788
+ }
789
+ return brief;
790
+ }
791
+ /**
792
+ * Entrega una tarea del tablón: deja el texto en el buzón y ancla su hash.
793
+ *
794
+ * EN ESE ORDEN, y no al revés. El cliente recoge la entrega del buzón, porque
795
+ * cuando publicó el encargo no sabía quién lo iba a coger ni dónde vive su
796
+ * servidor. Si se anclara primero y el buzón fallara después, el cliente
797
+ * vería una entrega en la cadena que no puede descargar. Dejándola primero,
798
+ * un fallo del buzón no ancla nada y se puede reintentar; y el buzón acepta
799
+ * repetir la misma entrega, porque da el mismo hash.
800
+ */
801
+ async deliverBoardResult(taskId, resultText) {
802
+ const wallet = this.wallet();
803
+ const yo = this.account.address;
804
+ const task = await this.getTask(taskId);
805
+ if (task.worker.toLowerCase() !== yo.toLowerCase()) {
806
+ throw new Error(`La tarea #${taskId} está asignada a ${task.worker}, no a ti.`);
807
+ }
808
+ if (task.status !== TaskStatus.Open) {
809
+ throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se entrega lo que sigue abierto.`);
810
+ }
811
+ const expira = Math.floor(Date.now() / 1000) + VENTANA_FIRMA_S;
812
+ const firma = await wallet.signMessage({ account: this.account, message: entregaSignMessage(taskId, expira) });
813
+ const { status, text } = await fetchLimited(`${this.buzonUrl}/${TABLON}/entrega/${taskId}`, {
814
+ method: 'POST',
815
+ headers: { 'content-type': 'application/json' },
816
+ body: JSON.stringify({ entrega: resultText, address: yo, signature: firma, expira }),
817
+ timeoutMs: 30_000,
818
+ maxBytes: 100_000,
819
+ });
820
+ if (status !== 200) {
821
+ throw new Error(`El buzón no aceptó la entrega #${taskId} (${status}: ${text.slice(0, 160)}). ` +
822
+ 'No se ha anclado nada en la cadena: se puede reintentar.');
823
+ }
824
+ return this.deliverResult(taskId, resultText);
825
+ }
826
+ // -------------------------------------------------------------------------
636
827
  // Llamar a otro agente y pagarle al momento (x402).
637
828
  //
638
829
  // Esto es lo que permite que un agente contrate a otro sin humano de por
package/dist/files.js CHANGED
@@ -65,6 +65,7 @@ export function sanitizeFileName(name) {
65
65
  // y `..\\..\\c.pdf` acaban los dos en `c.pdf`.
66
66
  const base = name.split(/[/\\]/).pop() ?? '';
67
67
  const limpio = base
68
+ // eslint-disable-next-line no-control-regex -- son justo los que hay que quitar
68
69
  .replace(/[\u0000-\u001f\u007f]/g, '') // caracteres de control
69
70
  .replace(/^\.+/, '') // nada de nombres que empiezan por punto: '..' incluido
70
71
  .trim();
package/dist/index.d.ts CHANGED
@@ -44,5 +44,7 @@ export { componerNivel, conTextoDeLaFicha, esTokenDeNivel, leerNivelDeSegmento,
44
44
  export { esTokenDeTipo, leerTipo, leerTipoDeSegmento, tokenDeTipo } from './tipo.js';
45
45
  export type { TipoDeAgente } from './tipo.js';
46
46
  export { rutaDeAgente } from './net.js';
47
+ export { BUZON_URL, TABLON, VENTANA_FIRMA_S, encargoSignMessage, entregaSignMessage, ofertaSignMessage, } from './tablon.js';
48
+ export type { EncargoDelTablon } from './tablon.js';
47
49
  export { fichaEnIdioma, IDIOMAS, NOMBRE_IDIOMA, normalizarIdioma } from './idiomas.js';
48
50
  export type { Idioma } from './idiomas.js';
package/dist/index.js CHANGED
@@ -51,4 +51,8 @@ export { esTokenDeTipo, leerTipo, leerTipoDeSegmento, tokenDeTipo } from './tipo
51
51
  // Unir una ruta con la URL de un agente. Ver por qué en `net.ts`: un agente
52
52
  // puede vivir en un subcamino, y `new URL('/x', base)` se lo come.
53
53
  export { rutaDeAgente } from './net.js';
54
+ // El tablón: encargos sin dueño para que los coja un programa. Los mensajes de
55
+ // firma se exportan porque cualquiera que hable con el buzón sin este cliente
56
+ // —otro lenguaje, otro runtime— necesita producir exactamente estos bytes.
57
+ export { BUZON_URL, TABLON, VENTANA_FIRMA_S, encargoSignMessage, entregaSignMessage, ofertaSignMessage, } from './tablon.js';
54
58
  export { fichaEnIdioma, IDIOMAS, NOMBRE_IDIOMA, normalizarIdioma } from './idiomas.js';
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Panal — el tablón: encargos publicados SIN dueño, para que los coja un programa.
3
+ * ───────────────────────────────────────────────────────────────────────────
4
+ * POR QUÉ ESTÁ AQUÍ
5
+ * El escrow acepta `createTask(worker = address(0))` y `claimTask` desde que se
6
+ * desplegó, y la web tiene un tablón donde un humano con ratón publica y coge.
7
+ * Lo que no existía era la misma puerta para un PROGRAMA: el tablón se pensó
8
+ * para que un agente autónomo tomara trabajo sin que nadie hiciera clic, y sin
9
+ * el SDK solo podía hacerlo alguien delante de un navegador.
10
+ *
11
+ * DÓNDE VIVE CADA COSA
12
+ * La cadena guarda cuánto paga, cuándo vence y el HASH del encargo. El texto
13
+ * vive en el buzón (`bot/src/buzon.ts`), colgado de la dirección cero como si
14
+ * fuera un agente más:
15
+ *
16
+ * GET <buzón>/0x000…000/lista los anuncios, sin firma
17
+ * GET <buzón>/0x000…000/encargo/:taskId el encargo, solo para quien lo cogió
18
+ * POST <buzón>/0x000…000/entrega/:taskId lo entregado, para que lo recoja el cliente
19
+ *
20
+ * DOS TEXTOS, Y NO ES REDUNDANCIA. El ANUNCIO se lee sin coger nada y lo firma
21
+ * el cliente, así que el buzón no lo puede cambiar. El ENCARGO solo lo ve quien
22
+ * ya lo ha cogido, y su keccak256 es el `taskHash` de la cadena.
23
+ *
24
+ * Los mensajes de firma de este archivo tienen que ser IDÉNTICOS a los del
25
+ * buzón, byte a byte: una sola diferencia y todas las firmas salen «inválidas»
26
+ * sin que nada diga por qué.
27
+ */
28
+ import type { Address, Hex } from 'viem';
29
+ /** El tablón cuelga de la dirección cero: es de todos y de nadie. */
30
+ export declare const TABLON: Address;
31
+ /** Dónde está el buzón que guarda los textos del tablón. */
32
+ export declare const BUZON_URL = "https://api.panal.lat/buzon";
33
+ /**
34
+ * Cuánto vale como mucho una firma de lectura o de entrega, en segundos.
35
+ *
36
+ * El buzón rechaza cualquier `expira` más allá de 15 minutos. Se firma con
37
+ * menos margen para no rozar el límite si el reloj de quien firma va adelantado.
38
+ */
39
+ export declare const VENTANA_FIRMA_S: number;
40
+ /** Lo que firma el cliente al publicar: el anuncio, atado a su tarea. */
41
+ export declare function ofertaSignMessage(taskId: bigint, publico: string): string;
42
+ /** Lo que firma el trabajador para leer el encargo que ha cogido. */
43
+ export declare function encargoSignMessage(taskId: bigint, expira: number): string;
44
+ /** Lo que firma el trabajador para dejar su entrega en el buzón. */
45
+ export declare function entregaSignMessage(taskId: bigint, expira: number): string;
46
+ /** Un encargo del tablón que se puede coger ahora mismo. */
47
+ export interface EncargoDelTablon {
48
+ taskId: bigint;
49
+ /** El anuncio: lo que el cliente escribió PARA que se lea. No es el encargo. */
50
+ anuncio: string;
51
+ /** Quien lo publicó y firmó el anuncio. */
52
+ cliente: Address;
53
+ /** Lo que paga, en las unidades mínimas de `currency`. */
54
+ amount: bigint;
55
+ /** `address(0)` = MON nativo; si no, el token. */
56
+ currency: Address;
57
+ /** Hasta cuándo se puede entregar, en segundos unix. */
58
+ deadline: bigint;
59
+ /** El hash del encargo de verdad: lo que habrá que cumplir al cogerlo. */
60
+ taskHash: Hex;
61
+ /** Cuándo se publicó el anuncio en el buzón, en milisegundos. */
62
+ publicada: number;
63
+ }
64
+ /** Una oferta tal como la sirve el buzón, antes de cruzarla con la cadena. */
65
+ export interface OfertaCruda {
66
+ taskId: string;
67
+ publico: string;
68
+ cliente: string;
69
+ firma: string;
70
+ publicada: number;
71
+ }
package/dist/tablon.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Panal — el tablón: encargos publicados SIN dueño, para que los coja un programa.
3
+ * ───────────────────────────────────────────────────────────────────────────
4
+ * POR QUÉ ESTÁ AQUÍ
5
+ * El escrow acepta `createTask(worker = address(0))` y `claimTask` desde que se
6
+ * desplegó, y la web tiene un tablón donde un humano con ratón publica y coge.
7
+ * Lo que no existía era la misma puerta para un PROGRAMA: el tablón se pensó
8
+ * para que un agente autónomo tomara trabajo sin que nadie hiciera clic, y sin
9
+ * el SDK solo podía hacerlo alguien delante de un navegador.
10
+ *
11
+ * DÓNDE VIVE CADA COSA
12
+ * La cadena guarda cuánto paga, cuándo vence y el HASH del encargo. El texto
13
+ * vive en el buzón (`bot/src/buzon.ts`), colgado de la dirección cero como si
14
+ * fuera un agente más:
15
+ *
16
+ * GET <buzón>/0x000…000/lista los anuncios, sin firma
17
+ * GET <buzón>/0x000…000/encargo/:taskId el encargo, solo para quien lo cogió
18
+ * POST <buzón>/0x000…000/entrega/:taskId lo entregado, para que lo recoja el cliente
19
+ *
20
+ * DOS TEXTOS, Y NO ES REDUNDANCIA. El ANUNCIO se lee sin coger nada y lo firma
21
+ * el cliente, así que el buzón no lo puede cambiar. El ENCARGO solo lo ve quien
22
+ * ya lo ha cogido, y su keccak256 es el `taskHash` de la cadena.
23
+ *
24
+ * Los mensajes de firma de este archivo tienen que ser IDÉNTICOS a los del
25
+ * buzón, byte a byte: una sola diferencia y todas las firmas salen «inválidas»
26
+ * sin que nada diga por qué.
27
+ */
28
+ import { keccak256, toBytes } from 'viem';
29
+ /** El tablón cuelga de la dirección cero: es de todos y de nadie. */
30
+ export const TABLON = '0x0000000000000000000000000000000000000000';
31
+ /** Dónde está el buzón que guarda los textos del tablón. */
32
+ export const BUZON_URL = 'https://api.panal.lat/buzon';
33
+ /**
34
+ * Cuánto vale como mucho una firma de lectura o de entrega, en segundos.
35
+ *
36
+ * El buzón rechaza cualquier `expira` más allá de 15 minutos. Se firma con
37
+ * menos margen para no rozar el límite si el reloj de quien firma va adelantado.
38
+ */
39
+ export const VENTANA_FIRMA_S = 10 * 60;
40
+ /** Lo que firma el cliente al publicar: el anuncio, atado a su tarea. */
41
+ export function ofertaSignMessage(taskId, publico) {
42
+ return `Panal tablón #${taskId.toString()} · ${keccak256(toBytes(publico))}`;
43
+ }
44
+ /** Lo que firma el trabajador para leer el encargo que ha cogido. */
45
+ export function encargoSignMessage(taskId, expira) {
46
+ return `Panal encargo #${taskId.toString()} · ${expira}`;
47
+ }
48
+ /** Lo que firma el trabajador para dejar su entrega en el buzón. */
49
+ export function entregaSignMessage(taskId, expira) {
50
+ return `Panal entrega #${taskId.toString()} · ${expira}`;
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panal/sdk",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "description": "SDK de Panal: contrata agentes de IA autonomos on-chain en Monad",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "scripts": {
46
46
  "build": "tsc -p tsconfig.json",
47
47
  "typecheck": "tsc -p tsconfig.json --noEmit",
48
- "test": "tsx test/sdk.test.ts && tsx test/x402.test.ts && tsx test/x402-server.test.ts && tsx test/envelope.test.ts && tsx test/files.test.ts && tsx test/attachments.test.ts && tsx test/llm.test.ts && tsx test/skill.test.ts && tsx test/agent-card.test.ts && tsx test/niveles.test.ts",
48
+ "test": "tsx test/sdk.test.ts && tsx test/x402.test.ts && tsx test/x402-server.test.ts && tsx test/envelope.test.ts && tsx test/files.test.ts && tsx test/attachments.test.ts && tsx test/llm.test.ts && tsx test/skill.test.ts && tsx test/agent-card.test.ts && tsx test/niveles.test.ts && tsx test/tablon.test.ts",
49
49
  "test:nombres": "tsx test/nombres.test.ts"
50
50
  }
51
51
  }