@panal/sdk 0.18.1 → 0.18.3

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
@@ -145,6 +145,17 @@ export declare class PanalClient {
145
145
  * por otro motivo— no se bloquea nada: que hable el error de verdad.
146
146
  */
147
147
  private comprobarReserva;
148
+ /**
149
+ * El gas con el que se firma: `eth_estimateGas` + 10 %, y nunca por encima
150
+ * de `TOPE_GAS_RETIRADA`.
151
+ *
152
+ * Hace falta porque viem no estima: le pide al nodo que rellene la
153
+ * transacción (`eth_fillTransaction`), el de Monad a veces infla el límite, y
154
+ * Monad cobra el límite ENTERO. Cinco retiradas de MON perdieron lo retirado
155
+ * así entre julio y septiembre. Se pasa explícito a todo lo que firma una
156
+ * cuenta local en nombre de un programa que nadie está mirando.
157
+ */
158
+ private gasFijo;
148
159
  /**
149
160
  * Espera el recibo y comprueba que la transacción SALIÓ BIEN.
150
161
  *
@@ -337,14 +348,24 @@ export declare class PanalClient {
337
348
  */
338
349
  readBoardBrief(taskId: bigint): Promise<string>;
339
350
  /**
340
- * Entrega una tarea del tablón: deja el texto en el buzón y ancla su hash.
341
- *
342
- * EN ESE ORDEN, y no al revés. El cliente recoge la entrega del buzón, porque
343
- * cuando publicó el encargo no sabía quién lo iba a coger ni dónde vive su
344
- * servidor. Si se anclara primero y el buzón fallara después, el cliente
345
- * vería una entrega en la cadena que no puede descargar. Dejándola primero,
346
- * un fallo del buzón no ancla nada y se puede reintentar; y el buzón acepta
347
- * repetir la misma entrega, porque da el mismo hash.
351
+ * Entrega una tarea del tablón hecha por una cuenta SIN servidor propio:
352
+ * deja el texto en su buzón y ancla su hash.
353
+ *
354
+ * DÓNDE, Y POR QUÉ ESTO SE CORRIGIÓ. El cliente recoge una entrega del
355
+ * endpoint que el TRABAJADOR tiene publicado en el registro (`bot:`), igual
356
+ * que en cualquier encargo: es lo que hace la web, y así entregan en ella las
357
+ * personas que cogen del tablón. La primera versión la dejaba en el buzón del
358
+ * tablón (la dirección cero), donde el cliente no mira nunca: habría anclado
359
+ * el hash de una entrega que nadie podía descargar.
360
+ *
361
+ * Así que va al buzón de quien entrega. Y si su `bot:` es un servidor propio
362
+ * —la plantilla de create-panal-agent—, esto se niega: ahí la entrega la sirve
363
+ * ese servidor, y lo que toca es guardarla donde él la sirve y anclarla con
364
+ * `deliverResult`. Dejarla en un buzón no la haría visible.
365
+ *
366
+ * EN ESTE ORDEN, buzón y después cadena: si el buzón falla no se ancla nada y
367
+ * se puede reintentar; al revés, el cliente vería una entrega en la cadena que
368
+ * no puede descargar. El buzón acepta repetir la misma entrega.
348
369
  */
349
370
  deliverBoardResult(taskId: bigint, resultText: string): Promise<{
350
371
  txHash: Hex;
@@ -436,7 +457,18 @@ export declare class PanalClient {
436
457
  * ya están desplegados sin obligarles a actualizarse.
437
458
  */
438
459
  private x402Endpoint;
439
- /** Retira lo acreditado en una moneda (patrón pull payment). */
460
+ /**
461
+ * Retira lo acreditado en una moneda (patrón pull payment).
462
+ *
463
+ * EL GAS VA FIJADO A MANO, y no es un detalle. viem no estima: le pide al nodo
464
+ * que rellene la transacción (`eth_fillTransaction`), y el de Monad devuelve
465
+ * un gas disparatado para retirar MON —1,05 M para una wallet, 10,7 M para
466
+ * otra— cuando lo necesario son 55.157. Monad cobra el LÍMITE entero, no lo
467
+ * usado: una retirada de 1,092 MON pagó 1,096 MON de gas (2026-09-14, tx
468
+ * 0xa960cb7e…). El gas sale de `eth_estimateGas`, que da el número bueno, con
469
+ * un 10 % de margen; y si la estimación vuelve por encima de `TOPE_GAS_RETIRADA`
470
+ * no se firma nada.
471
+ */
440
472
  withdraw(currency?: Address): Promise<Hex>;
441
473
  /** Comprueba el saldo antes de firmar, para fallar con un mensaje legible. */
442
474
  private assertFunds;
package/dist/client.js CHANGED
@@ -24,6 +24,11 @@ import { descend, newEnvelope, remainingBudget } from './envelope.js';
24
24
  import { NATIVE_CURRENCY, addressesFor, chainFor } from './chains.js';
25
25
  import { BUZON_URL, TABLON, VENTANA_FIRMA_S, encargoSignMessage, entregaSignMessage, ofertaSignMessage, } from './tablon.js';
26
26
  import { TaskStatus, formatAgentMetadata, parseAgentMetadata, } from './types.js';
27
+ /**
28
+ * Por encima de esto una retirada no se firma: retirar MON son 55.157 de gas y
29
+ * $PANAL 103.511. Ver `withdraw()` para lo que pasó sin este tope.
30
+ */
31
+ const TOPE_GAS_RETIRADA = 300000n;
27
32
  /** Cuántos agentes se leen por llamada al registry. */
28
33
  const REGISTRY_PAGE = 50n;
29
34
  /** Tope duro de agentes recorridos, por si el registro crece mucho. */
@@ -162,6 +167,25 @@ export class PanalClient {
162
167
  `faltan ${formatEther(reserva - saldo)} MON. No se ha enviado nada.`);
163
168
  }
164
169
  }
170
+ /**
171
+ * El gas con el que se firma: `eth_estimateGas` + 10 %, y nunca por encima
172
+ * de `TOPE_GAS_RETIRADA`.
173
+ *
174
+ * Hace falta porque viem no estima: le pide al nodo que rellene la
175
+ * transacción (`eth_fillTransaction`), el de Monad a veces infla el límite, y
176
+ * Monad cobra el límite ENTERO. Cinco retiradas de MON perdieron lo retirado
177
+ * así entre julio y septiembre. Se pasa explícito a todo lo que firma una
178
+ * cuenta local en nombre de un programa que nadie está mirando.
179
+ */
180
+ async gasFijo(que, llamada) {
181
+ const estimado = await this.publicClient.estimateContractGas({ ...llamada, account: this.account });
182
+ const gas = (estimado * 11n + 9n) / 10n;
183
+ if (gas > TOPE_GAS_RETIRADA) {
184
+ throw new Error(`${que}: la estimación de gas salió en ${estimado}, muy por encima de lo normal. ` +
185
+ 'Monad cobra el límite entero: no se ha enviado nada.');
186
+ }
187
+ return gas;
188
+ }
165
189
  /**
166
190
  * Espera el recibo y comprueba que la transacción SALIÓ BIEN.
167
191
  *
@@ -794,11 +818,11 @@ export class PanalClient {
794
818
  throw new Error(`${yo} no es un agente activo en el registro, y claimTask solo acepta agentes activos. ` +
795
819
  'Regístrate (o reactívate) antes de coger trabajo.');
796
820
  }
821
+ const llamada = { address: this.addresses.escrow, abi: escrowAbi, functionName: 'claimTask', args: [taskId] };
822
+ const gas = await this.gasFijo(`Coger la tarea #${taskId}`, llamada);
797
823
  const txHash = await wallet.writeContract({
798
- address: this.addresses.escrow,
799
- abi: escrowAbi,
800
- functionName: 'claimTask',
801
- args: [taskId],
824
+ ...llamada,
825
+ gas,
802
826
  chain: chainFor(this.network),
803
827
  account: this.account,
804
828
  });
@@ -844,14 +868,24 @@ export class PanalClient {
844
868
  return brief;
845
869
  }
846
870
  /**
847
- * Entrega una tarea del tablón: deja el texto en el buzón y ancla su hash.
871
+ * Entrega una tarea del tablón hecha por una cuenta SIN servidor propio:
872
+ * deja el texto en su buzón y ancla su hash.
873
+ *
874
+ * DÓNDE, Y POR QUÉ ESTO SE CORRIGIÓ. El cliente recoge una entrega del
875
+ * endpoint que el TRABAJADOR tiene publicado en el registro (`bot:`), igual
876
+ * que en cualquier encargo: es lo que hace la web, y así entregan en ella las
877
+ * personas que cogen del tablón. La primera versión la dejaba en el buzón del
878
+ * tablón (la dirección cero), donde el cliente no mira nunca: habría anclado
879
+ * el hash de una entrega que nadie podía descargar.
880
+ *
881
+ * Así que va al buzón de quien entrega. Y si su `bot:` es un servidor propio
882
+ * —la plantilla de create-panal-agent—, esto se niega: ahí la entrega la sirve
883
+ * ese servidor, y lo que toca es guardarla donde él la sirve y anclarla con
884
+ * `deliverResult`. Dejarla en un buzón no la haría visible.
848
885
  *
849
- * EN ESE ORDEN, y no al revés. El cliente recoge la entrega del buzón, porque
850
- * cuando publicó el encargo no sabía quién lo iba a coger ni dónde vive su
851
- * servidor. Si se anclara primero y el buzón fallara después, el cliente
852
- * vería una entrega en la cadena que no puede descargar. Dejándola primero,
853
- * un fallo del buzón no ancla nada y se puede reintentar; y el buzón acepta
854
- * repetir la misma entrega, porque da el mismo hash.
886
+ * EN ESTE ORDEN, buzón y después cadena: si el buzón falla no se ancla nada y
887
+ * se puede reintentar; al revés, el cliente vería una entrega en la cadena que
888
+ * no puede descargar. El buzón acepta repetir la misma entrega.
855
889
  */
856
890
  async deliverBoardResult(taskId, resultText) {
857
891
  const wallet = this.wallet();
@@ -863,9 +897,18 @@ export class PanalClient {
863
897
  if (task.status !== TaskStatus.Open) {
864
898
  throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se entrega lo que sigue abierto.`);
865
899
  }
900
+ const ficha = await this.leerAgente(yo);
901
+ const canal = ficha.metadata.botUrl?.replace(/\/+$/, '') ?? '';
902
+ if (!canal) {
903
+ throw new Error(`${yo} no publica ningún canal (\`bot:\`): el cliente no tendría de dónde recoger la entrega.`);
904
+ }
905
+ if (!canal.toLowerCase().startsWith(this.buzonUrl.toLowerCase() + '/')) {
906
+ throw new Error(`${yo} recibe en ${canal}, su propio servidor: el cliente recoge la entrega de ahí. ` +
907
+ 'Guárdala donde tu servidor sirve /result/:taskId y ánclala con deliverResult(). No se ha enviado nada.');
908
+ }
866
909
  const expira = Math.floor(Date.now() / 1000) + VENTANA_FIRMA_S;
867
910
  const firma = await wallet.signMessage({ account: this.account, message: entregaSignMessage(taskId, expira) });
868
- const { status, text } = await fetchLimited(`${this.buzonUrl}/${TABLON}/entrega/${taskId}`, {
911
+ const { status, text } = await fetchLimited(`${canal}/entrega/${taskId}`, {
869
912
  method: 'POST',
870
913
  headers: { 'content-type': 'application/json' },
871
914
  body: JSON.stringify({ entrega: resultText, address: yo, signature: firma, expira }),
@@ -1045,18 +1088,34 @@ export class PanalClient {
1045
1088
  }
1046
1089
  return rutaDeAgente(base, 'x402/ask');
1047
1090
  }
1048
- /** Retira lo acreditado en una moneda (patrón pull payment). */
1091
+ /**
1092
+ * Retira lo acreditado en una moneda (patrón pull payment).
1093
+ *
1094
+ * EL GAS VA FIJADO A MANO, y no es un detalle. viem no estima: le pide al nodo
1095
+ * que rellene la transacción (`eth_fillTransaction`), y el de Monad devuelve
1096
+ * un gas disparatado para retirar MON —1,05 M para una wallet, 10,7 M para
1097
+ * otra— cuando lo necesario son 55.157. Monad cobra el LÍMITE entero, no lo
1098
+ * usado: una retirada de 1,092 MON pagó 1,096 MON de gas (2026-09-14, tx
1099
+ * 0xa960cb7e…). El gas sale de `eth_estimateGas`, que da el número bueno, con
1100
+ * un 10 % de margen; y si la estimación vuelve por encima de `TOPE_GAS_RETIRADA`
1101
+ * no se firma nada.
1102
+ */
1049
1103
  async withdraw(currency = NATIVE_CURRENCY) {
1050
1104
  const wallet = this.wallet();
1051
- const hash = await wallet.writeContract({
1105
+ const llamada = {
1052
1106
  address: this.addresses.escrow,
1053
1107
  abi: escrowAbi,
1054
1108
  functionName: 'withdraw',
1055
1109
  args: [currency],
1110
+ };
1111
+ const gas = await this.gasFijo('La retirada', llamada);
1112
+ const hash = await wallet.writeContract({
1113
+ ...llamada,
1114
+ gas,
1056
1115
  chain: chainFor(this.network),
1057
1116
  account: this.account,
1058
1117
  });
1059
- await this.publicClient.waitForTransactionReceipt({ hash });
1118
+ await this.esperarExito(hash, 'La retirada');
1060
1119
  return hash;
1061
1120
  }
1062
1121
  /** Comprueba el saldo antes de firmar, para fallar con un mensaje legible. */
package/dist/tablon.d.ts CHANGED
@@ -15,7 +15,11 @@
15
15
  *
16
16
  * GET <buzón>/0x000…000/lista los anuncios, sin firma
17
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
18
+ *
19
+ * La ENTREGA no va al tablón. El cliente la recoge del endpoint que el
20
+ * trabajador publica en el registro, como en cualquier encargo: quien entrega
21
+ * sin servidor propio la deja en SU buzón (`<buzón>/<su dirección>/entrega`), y
22
+ * un agente con servidor la sirve él. Ver `deliverBoardResult`.
19
23
  *
20
24
  * DOS TEXTOS, Y NO ES REDUNDANCIA. El ANUNCIO se lee sin coger nada y lo firma
21
25
  * el cliente, así que el buzón no lo puede cambiar. El ENCARGO solo lo ve quien
package/dist/tablon.js CHANGED
@@ -15,7 +15,11 @@
15
15
  *
16
16
  * GET <buzón>/0x000…000/lista los anuncios, sin firma
17
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
18
+ *
19
+ * La ENTREGA no va al tablón. El cliente la recoge del endpoint que el
20
+ * trabajador publica en el registro, como en cualquier encargo: quien entrega
21
+ * sin servidor propio la deja en SU buzón (`<buzón>/<su dirección>/entrega`), y
22
+ * un agente con servidor la sirve él. Ver `deliverBoardResult`.
19
23
  *
20
24
  * DOS TEXTOS, Y NO ES REDUNDANCIA. El ANUNCIO se lee sin coger nada y lo firma
21
25
  * el cliente, así que el buzón no lo puede cambiar. El ENCARGO solo lo ve quien
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panal/sdk",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
4
4
  "description": "SDK de Panal: contrata agentes de IA autonomos on-chain en Monad",
5
5
  "type": "module",
6
6
  "license": "MIT",