@panal/sdk 0.18.2 → 0.19.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/LICENSE +21 -0
- package/dist/client.d.ts +38 -8
- package/dist/client.js +94 -56
- package/dist/currencies.d.ts +73 -0
- package/dist/currencies.js +112 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/tablon.d.ts +5 -1
- package/dist/tablon.js +5 -1
- package/dist/x402-server.d.ts +94 -3
- package/dist/x402-server.js +290 -41
- package/dist/x402.d.ts +8 -2
- package/dist/x402.js +109 -35
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Panal and Kreno (founder: Gustavo Chura C.)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/client.d.ts
CHANGED
|
@@ -145,6 +145,19 @@ 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: contratar,
|
|
157
|
+
* aprobar, entregar, disputar, darse de alta. Antes solo la retirada y el
|
|
158
|
+
* tablón, y un agente entrega en cada encargo.
|
|
159
|
+
*/
|
|
160
|
+
private gasFijo;
|
|
148
161
|
/**
|
|
149
162
|
* Espera el recibo y comprueba que la transacción SALIÓ BIEN.
|
|
150
163
|
*
|
|
@@ -337,14 +350,24 @@ export declare class PanalClient {
|
|
|
337
350
|
*/
|
|
338
351
|
readBoardBrief(taskId: bigint): Promise<string>;
|
|
339
352
|
/**
|
|
340
|
-
* Entrega una tarea del tablón
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
353
|
+
* Entrega una tarea del tablón hecha por una cuenta SIN servidor propio:
|
|
354
|
+
* deja el texto en su buzón y ancla su hash.
|
|
355
|
+
*
|
|
356
|
+
* DÓNDE, Y POR QUÉ ESTO SE CORRIGIÓ. El cliente recoge una entrega del
|
|
357
|
+
* endpoint que el TRABAJADOR tiene publicado en el registro (`bot:`), igual
|
|
358
|
+
* que en cualquier encargo: es lo que hace la web, y así entregan en ella las
|
|
359
|
+
* personas que cogen del tablón. La primera versión la dejaba en el buzón del
|
|
360
|
+
* tablón (la dirección cero), donde el cliente no mira nunca: habría anclado
|
|
361
|
+
* el hash de una entrega que nadie podía descargar.
|
|
362
|
+
*
|
|
363
|
+
* Así que va al buzón de quien entrega. Y si su `bot:` es un servidor propio
|
|
364
|
+
* —la plantilla de create-panal-agent—, esto se niega: ahí la entrega la sirve
|
|
365
|
+
* ese servidor, y lo que toca es guardarla donde él la sirve y anclarla con
|
|
366
|
+
* `deliverResult`. Dejarla en un buzón no la haría visible.
|
|
367
|
+
*
|
|
368
|
+
* EN ESTE ORDEN, buzón y después cadena: si el buzón falla no se ancla nada y
|
|
369
|
+
* se puede reintentar; al revés, el cliente vería una entrega en la cadena que
|
|
370
|
+
* no puede descargar. El buzón acepta repetir la misma entrega.
|
|
348
371
|
*/
|
|
349
372
|
deliverBoardResult(taskId: bigint, resultText: string): Promise<{
|
|
350
373
|
txHash: Hex;
|
|
@@ -424,6 +447,13 @@ export declare class PanalClient {
|
|
|
424
447
|
* cuesta ni una petición.
|
|
425
448
|
*/
|
|
426
449
|
skillsPermitidas?: string[];
|
|
450
|
+
/**
|
|
451
|
+
* En qué moneda se paga, y por tanto en qué unidades va `maxSpend`.
|
|
452
|
+
* Por defecto $PANAL. Solo se cotiza con quien cobre en ESTA: comparar
|
|
453
|
+
* precios de monedas distintas era comparar 1 USDC con 1 wei de $PANAL,
|
|
454
|
+
* y un tope en una moneda no dice nada de otra.
|
|
455
|
+
*/
|
|
456
|
+
asset?: Address;
|
|
427
457
|
}): Promise<AskResult & {
|
|
428
458
|
agent: Address;
|
|
429
459
|
skill: string;
|
package/dist/client.js
CHANGED
|
@@ -29,6 +29,13 @@ import { TaskStatus, formatAgentMetadata, parseAgentMetadata, } from './types.js
|
|
|
29
29
|
* $PANAL 103.511. Ver `withdraw()` para lo que pasó sin este tope.
|
|
30
30
|
*/
|
|
31
31
|
const TOPE_GAS_RETIRADA = 300000n;
|
|
32
|
+
/**
|
|
33
|
+
* El tope para dar de alta o cambiar la ficha. Guardar texto en la cadena
|
|
34
|
+
* cuesta por cada 32 bytes, y una ficha con el logo dentro es grande: aquí el
|
|
35
|
+
* tope solo está para cortar un disparate, no para medir lo normal. Lo normal
|
|
36
|
+
* lo da `eth_estimateGas`, que es fiable; el que inflaba era `eth_fillTransaction`.
|
|
37
|
+
*/
|
|
38
|
+
const TOPE_GAS_FICHA = 30000000n;
|
|
32
39
|
/** Cuántos agentes se leen por llamada al registry. */
|
|
33
40
|
const REGISTRY_PAGE = 50n;
|
|
34
41
|
/** Tope duro de agentes recorridos, por si el registro crece mucho. */
|
|
@@ -167,6 +174,27 @@ export class PanalClient {
|
|
|
167
174
|
`faltan ${formatEther(reserva - saldo)} MON. No se ha enviado nada.`);
|
|
168
175
|
}
|
|
169
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* El gas con el que se firma: `eth_estimateGas` + 10 %, y nunca por encima
|
|
179
|
+
* de `TOPE_GAS_RETIRADA`.
|
|
180
|
+
*
|
|
181
|
+
* Hace falta porque viem no estima: le pide al nodo que rellene la
|
|
182
|
+
* transacción (`eth_fillTransaction`), el de Monad a veces infla el límite, y
|
|
183
|
+
* Monad cobra el límite ENTERO. Cinco retiradas de MON perdieron lo retirado
|
|
184
|
+
* así entre julio y septiembre. Se pasa explícito a TODO lo que firma una
|
|
185
|
+
* cuenta local en nombre de un programa que nadie está mirando: contratar,
|
|
186
|
+
* aprobar, entregar, disputar, darse de alta. Antes solo la retirada y el
|
|
187
|
+
* tablón, y un agente entrega en cada encargo.
|
|
188
|
+
*/
|
|
189
|
+
async gasFijo(que, llamada, tope = TOPE_GAS_RETIRADA) {
|
|
190
|
+
const estimado = await this.publicClient.estimateContractGas({ ...llamada, account: this.account });
|
|
191
|
+
const gas = (estimado * 11n + 9n) / 10n;
|
|
192
|
+
if (gas > tope) {
|
|
193
|
+
throw new Error(`${que}: la estimación de gas salió en ${estimado}, muy por encima de lo normal. ` +
|
|
194
|
+
'Monad cobra el límite entero: no se ha enviado nada.');
|
|
195
|
+
}
|
|
196
|
+
return gas;
|
|
197
|
+
}
|
|
170
198
|
/**
|
|
171
199
|
* Espera el recibo y comprueba que la transacción SALIÓ BIEN.
|
|
172
200
|
*
|
|
@@ -456,22 +484,19 @@ export class PanalClient {
|
|
|
456
484
|
if (!isNative) {
|
|
457
485
|
// approve por el importe exacto, no infinito: si el escrow tuviera un
|
|
458
486
|
// fallo, la exposición se limita a este encargo.
|
|
487
|
+
const aFirmar = { address: agent.currency, abi: erc20Abi, functionName: 'approve', args: [this.addresses.escrow, amount] };
|
|
459
488
|
const approveHash = await wallet.writeContract({
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
functionName: 'approve',
|
|
463
|
-
args: [this.addresses.escrow, amount],
|
|
489
|
+
...aFirmar,
|
|
490
|
+
gas: await this.gasFijo('El approve', aFirmar),
|
|
464
491
|
chain: chainFor(this.network),
|
|
465
492
|
account: this.account,
|
|
466
493
|
});
|
|
467
494
|
await this.publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
468
495
|
}
|
|
496
|
+
const aFirmar = { address: this.addresses.escrow, abi: escrowAbi, functionName: 'createTask', args: [agent.address, taskHash, deadline, agent.currency, amount], value: isNative ? amount : 0n };
|
|
469
497
|
const txHash = await wallet.writeContract({
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
functionName: 'createTask',
|
|
473
|
-
args: [agent.address, taskHash, deadline, agent.currency, amount],
|
|
474
|
-
value: isNative ? amount : 0n,
|
|
498
|
+
...aFirmar,
|
|
499
|
+
gas: await this.gasFijo('El encargo', aFirmar),
|
|
475
500
|
chain: chainFor(this.network),
|
|
476
501
|
account: this.account,
|
|
477
502
|
});
|
|
@@ -498,11 +523,10 @@ export class PanalClient {
|
|
|
498
523
|
if (task.status !== TaskStatus.Delivered) {
|
|
499
524
|
throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se aprueba lo entregado.`);
|
|
500
525
|
}
|
|
526
|
+
const aFirmar = { address: this.addresses.escrow, abi: escrowAbi, functionName: 'approveAndRelease', args: [taskId, rating] };
|
|
501
527
|
const hash = await wallet.writeContract({
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
functionName: 'approveAndRelease',
|
|
505
|
-
args: [taskId, rating],
|
|
528
|
+
...aFirmar,
|
|
529
|
+
gas: await this.gasFijo('La aprobación', aFirmar),
|
|
506
530
|
chain: chainFor(this.network),
|
|
507
531
|
account: this.account,
|
|
508
532
|
});
|
|
@@ -531,11 +555,10 @@ export class PanalClient {
|
|
|
531
555
|
if (task.status !== TaskStatus.Open) {
|
|
532
556
|
throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se cancela lo que sigue abierto.`);
|
|
533
557
|
}
|
|
558
|
+
const aFirmar = { address: this.addresses.escrow, abi: escrowAbi, functionName: 'cancelTask', args: [taskId] };
|
|
534
559
|
const hash = await wallet.writeContract({
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
functionName: 'cancelTask',
|
|
538
|
-
args: [taskId],
|
|
560
|
+
...aFirmar,
|
|
561
|
+
gas: await this.gasFijo('La cancelación', aFirmar),
|
|
539
562
|
chain: chainFor(this.network),
|
|
540
563
|
account: this.account,
|
|
541
564
|
});
|
|
@@ -559,11 +582,10 @@ export class PanalClient {
|
|
|
559
582
|
if (task.status !== TaskStatus.Delivered) {
|
|
560
583
|
throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se disputa lo entregado.`);
|
|
561
584
|
}
|
|
585
|
+
const aFirmar = { address: this.addresses.escrow, abi: escrowAbi, functionName: 'openDispute', args: [taskId] };
|
|
562
586
|
const hash = await wallet.writeContract({
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
functionName: 'openDispute',
|
|
566
|
-
args: [taskId],
|
|
587
|
+
...aFirmar,
|
|
588
|
+
gas: await this.gasFijo('La disputa', aFirmar),
|
|
567
589
|
chain: chainFor(this.network),
|
|
568
590
|
account: this.account,
|
|
569
591
|
});
|
|
@@ -591,6 +613,7 @@ export class PanalClient {
|
|
|
591
613
|
...llamada,
|
|
592
614
|
abi: registryAbi,
|
|
593
615
|
functionName: 'registerAgent',
|
|
616
|
+
gas: await this.gasFijo('El alta', llamada, TOPE_GAS_FICHA),
|
|
594
617
|
chain: chainFor(this.network),
|
|
595
618
|
account: this.account,
|
|
596
619
|
});
|
|
@@ -600,11 +623,10 @@ export class PanalClient {
|
|
|
600
623
|
/** Cambia el nombre, la descripción, las skills o el endpoint publicados. */
|
|
601
624
|
async updateMetadata(metadata) {
|
|
602
625
|
const wallet = this.wallet();
|
|
626
|
+
const aFirmar = { address: this.addresses.registry, abi: registryAbi, functionName: 'updateMetadata', args: [formatAgentMetadata(metadata)] };
|
|
603
627
|
const hash = await wallet.writeContract({
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
functionName: 'updateMetadata',
|
|
607
|
-
args: [formatAgentMetadata(metadata)],
|
|
628
|
+
...aFirmar,
|
|
629
|
+
gas: await this.gasFijo('El cambio de ficha', aFirmar, TOPE_GAS_FICHA),
|
|
608
630
|
chain: chainFor(this.network),
|
|
609
631
|
account: this.account,
|
|
610
632
|
});
|
|
@@ -614,11 +636,10 @@ export class PanalClient {
|
|
|
614
636
|
/** Cambia el precio por tarea, y opcionalmente la moneda en la que cobras. */
|
|
615
637
|
async updatePrice(pricePerTask, currency = NATIVE_CURRENCY) {
|
|
616
638
|
const wallet = this.wallet();
|
|
639
|
+
const aFirmar = { address: this.addresses.registry, abi: registryAbi, functionName: 'updatePrice', args: [pricePerTask, currency] };
|
|
617
640
|
const hash = await wallet.writeContract({
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
functionName: 'updatePrice',
|
|
621
|
-
args: [pricePerTask, currency],
|
|
641
|
+
...aFirmar,
|
|
642
|
+
gas: await this.gasFijo('El cambio de precio', aFirmar),
|
|
622
643
|
chain: chainFor(this.network),
|
|
623
644
|
account: this.account,
|
|
624
645
|
});
|
|
@@ -632,11 +653,10 @@ export class PanalClient {
|
|
|
632
653
|
*/
|
|
633
654
|
async setActive(active) {
|
|
634
655
|
const wallet = this.wallet();
|
|
656
|
+
const aFirmar = { address: this.addresses.registry, abi: registryAbi, functionName: 'setActive', args: [active] };
|
|
635
657
|
const hash = await wallet.writeContract({
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
functionName: 'setActive',
|
|
639
|
-
args: [active],
|
|
658
|
+
...aFirmar,
|
|
659
|
+
gas: await this.gasFijo('El cambio de estado', aFirmar),
|
|
640
660
|
chain: chainFor(this.network),
|
|
641
661
|
account: this.account,
|
|
642
662
|
});
|
|
@@ -660,11 +680,10 @@ export class PanalClient {
|
|
|
660
680
|
throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se entrega lo que sigue abierto.`);
|
|
661
681
|
}
|
|
662
682
|
const resultHash = keccak256(toBytes(resultText));
|
|
683
|
+
const aFirmar = { address: this.addresses.escrow, abi: escrowAbi, functionName: 'deliverResult', args: [taskId, resultHash] };
|
|
663
684
|
const txHash = await wallet.writeContract({
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
functionName: 'deliverResult',
|
|
667
|
-
args: [taskId, resultHash],
|
|
685
|
+
...aFirmar,
|
|
686
|
+
gas: await this.gasFijo('La entrega', aFirmar),
|
|
668
687
|
chain: chainFor(this.network),
|
|
669
688
|
account: this.account,
|
|
670
689
|
});
|
|
@@ -799,11 +818,11 @@ export class PanalClient {
|
|
|
799
818
|
throw new Error(`${yo} no es un agente activo en el registro, y claimTask solo acepta agentes activos. ` +
|
|
800
819
|
'Regístrate (o reactívate) antes de coger trabajo.');
|
|
801
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);
|
|
802
823
|
const txHash = await wallet.writeContract({
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
functionName: 'claimTask',
|
|
806
|
-
args: [taskId],
|
|
824
|
+
...llamada,
|
|
825
|
+
gas,
|
|
807
826
|
chain: chainFor(this.network),
|
|
808
827
|
account: this.account,
|
|
809
828
|
});
|
|
@@ -849,14 +868,24 @@ export class PanalClient {
|
|
|
849
868
|
return brief;
|
|
850
869
|
}
|
|
851
870
|
/**
|
|
852
|
-
* Entrega una tarea del tablón
|
|
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.
|
|
853
885
|
*
|
|
854
|
-
* EN
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
* vería una entrega en la cadena que no puede descargar. Dejándola primero,
|
|
858
|
-
* un fallo del buzón no ancla nada y se puede reintentar; y el buzón acepta
|
|
859
|
-
* 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.
|
|
860
889
|
*/
|
|
861
890
|
async deliverBoardResult(taskId, resultText) {
|
|
862
891
|
const wallet = this.wallet();
|
|
@@ -868,9 +897,18 @@ export class PanalClient {
|
|
|
868
897
|
if (task.status !== TaskStatus.Open) {
|
|
869
898
|
throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se entrega lo que sigue abierto.`);
|
|
870
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
|
+
}
|
|
871
909
|
const expira = Math.floor(Date.now() / 1000) + VENTANA_FIRMA_S;
|
|
872
910
|
const firma = await wallet.signMessage({ account: this.account, message: entregaSignMessage(taskId, expira) });
|
|
873
|
-
const { status, text } = await fetchLimited(`${
|
|
911
|
+
const { status, text } = await fetchLimited(`${canal}/entrega/${taskId}`, {
|
|
874
912
|
method: 'POST',
|
|
875
913
|
headers: { 'content-type': 'application/json' },
|
|
876
914
|
body: JSON.stringify({ entrega: resultText, address: yo, signature: firma, expira }),
|
|
@@ -978,6 +1016,7 @@ export class PanalClient {
|
|
|
978
1016
|
? ` Se probó también con ${variantesDeSkill(skill).slice(1).map((v) => `"${v}"`).join(' y ')}.`
|
|
979
1017
|
: ''));
|
|
980
1018
|
}
|
|
1019
|
+
const moneda = getAddress(options.asset ?? this.addresses.panalToken);
|
|
981
1020
|
const quotes = [];
|
|
982
1021
|
const rechazos = [];
|
|
983
1022
|
for (const agent of candidates) {
|
|
@@ -988,7 +1027,10 @@ export class PanalClient {
|
|
|
988
1027
|
...options,
|
|
989
1028
|
envelope: heredado,
|
|
990
1029
|
});
|
|
991
|
-
if (
|
|
1030
|
+
if (getAddress(accept.asset) !== moneda) {
|
|
1031
|
+
rechazos.push(`${agent.metadata.name || agent.address}: cobra en ${accept.asset}, no en ${moneda}`);
|
|
1032
|
+
}
|
|
1033
|
+
else if (BigInt(accept.amount) <= tope)
|
|
992
1034
|
quotes.push({ agent, endpoint, accept });
|
|
993
1035
|
else
|
|
994
1036
|
rechazos.push(`${agent.metadata.name || agent.address}: pide ${accept.amount}, por encima del tope`);
|
|
@@ -1010,6 +1052,7 @@ export class PanalClient {
|
|
|
1010
1052
|
maxSpend: tope,
|
|
1011
1053
|
chainId: chainFor(this.network).id,
|
|
1012
1054
|
expectedPayee: elegido.agent.address,
|
|
1055
|
+
asset: moneda,
|
|
1013
1056
|
quote: elegido.accept,
|
|
1014
1057
|
envelope: siguiente,
|
|
1015
1058
|
});
|
|
@@ -1070,12 +1113,7 @@ export class PanalClient {
|
|
|
1070
1113
|
functionName: 'withdraw',
|
|
1071
1114
|
args: [currency],
|
|
1072
1115
|
};
|
|
1073
|
-
const
|
|
1074
|
-
const gas = (estimado * 11n + 9n) / 10n;
|
|
1075
|
-
if (gas > TOPE_GAS_RETIRADA) {
|
|
1076
|
-
throw new Error(`La estimación de gas para retirar salió en ${estimado}, muy por encima de lo normal (~55.000 en MON, ` +
|
|
1077
|
-
`~104.000 en $PANAL). Monad cobra el límite entero: no se ha enviado nada.`);
|
|
1078
|
-
}
|
|
1116
|
+
const gas = await this.gasFijo('La retirada', llamada);
|
|
1079
1117
|
const hash = await wallet.writeContract({
|
|
1080
1118
|
...llamada,
|
|
1081
1119
|
gas,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Las monedas con las que se paga una consulta x402 en Panal.
|
|
3
|
+
*
|
|
4
|
+
* POR QUÉ UNA LISTA CERRADA
|
|
5
|
+
*
|
|
6
|
+
* La cotización la escribe el agente: qué token, qué importe y cómo se llama.
|
|
7
|
+
* Fiarse de eso para enseñar el precio era el agujero. Un agente podía cobrar
|
|
8
|
+
* en un token de 6 decimales y llamarlo «$PANAL»: 1.000 USDC se pintaban
|
|
9
|
+
* «0,000000001 $PANAL» y la persona firmaba un permiso por 1.000 USDC creyendo
|
|
10
|
+
* que no pagaba nada.
|
|
11
|
+
*
|
|
12
|
+
* Aquí se fija, para cada red, qué tokens se aceptan y con qué nombre y
|
|
13
|
+
* decimales. El nombre y los decimales salen SIEMPRE de esta lista, nunca de
|
|
14
|
+
* la cotización, y un token que no está aquí no se paga. Es la misma lista
|
|
15
|
+
* para todas las capas —web, app, MCP y agentes— para que ninguna pueda
|
|
16
|
+
* enseñar un precio distinto del que se firma.
|
|
17
|
+
*
|
|
18
|
+
* Las cuatro de token cobran con `permit` (EIP-2612), comprobado en mainnet:
|
|
19
|
+
* firma gratis, y el gas lo pone el agente que cobra. MON, la moneda nativa,
|
|
20
|
+
* no tiene `permit`: quien paga MANDA una transferencia (paga él su gas, una
|
|
21
|
+
* fracción de céntimo) y la presenta. Ver `native-transfer` en x402-server.ts.
|
|
22
|
+
*
|
|
23
|
+
* - MON — la moneda de Monad. Nadie puede congelarla.
|
|
24
|
+
* - $PANAL — el token de Panal.
|
|
25
|
+
* - GHO — de Aave. Respaldo cripto, sin lista negra ni pausa: nadie puede
|
|
26
|
+
* congelarlo. La opción estable que no depende de una empresa.
|
|
27
|
+
* - USDC — de Circle. Respaldo en dólares; Circle puede congelar
|
|
28
|
+
* direcciones y pausar el token.
|
|
29
|
+
* - AUSD — de Agora. Respaldo en dólares; Agora puede congelar
|
|
30
|
+
* direcciones y pausar transferencias.
|
|
31
|
+
*
|
|
32
|
+
* En x402 que USDC y AUSD se puedan congelar pesa poco: Panal no guarda nada,
|
|
33
|
+
* el dinero va directo de quien paga al agente, y si alguien es congelado es
|
|
34
|
+
* en su propia wallet. Cada uno elige con qué paga o cobra.
|
|
35
|
+
*/
|
|
36
|
+
import { type Address } from 'viem';
|
|
37
|
+
import type { PanalNetwork } from './chains.js';
|
|
38
|
+
/**
|
|
39
|
+
* Cómo se cobra en una moneda: con una firma de `permit` que ejecuta quien
|
|
40
|
+
* cobra, o con una transferencia que manda quien paga.
|
|
41
|
+
*/
|
|
42
|
+
export type X402Scheme = 'eip2612-permit' | 'native-transfer';
|
|
43
|
+
/** La moneda nativa, como la escribe Panal en todas partes: la dirección cero. */
|
|
44
|
+
export declare const X402_NATIVE: Address;
|
|
45
|
+
export interface X402Currency {
|
|
46
|
+
/** Como se enseña: `$PANAL`, `GHO`, `USDC`, `AUSD`. */
|
|
47
|
+
symbol: string;
|
|
48
|
+
address: Address;
|
|
49
|
+
decimals: number;
|
|
50
|
+
scheme: X402Scheme;
|
|
51
|
+
/** Si su emisor puede congelar direcciones o pausar el token. */
|
|
52
|
+
issuerCanFreeze: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Las monedas aceptadas en una red, en el orden en que se ofrecen. */
|
|
55
|
+
export declare function x402Currencies(network?: PanalNetwork): readonly X402Currency[];
|
|
56
|
+
/** La red de un chainId de Monad, o `null` si no es ninguna de las dos. */
|
|
57
|
+
export declare function networkOfChain(chainId: number): PanalNetwork | null;
|
|
58
|
+
/**
|
|
59
|
+
* La moneda de un token, o `null` si Panal no acepta pagar en él.
|
|
60
|
+
*
|
|
61
|
+
* Por dirección: el nombre lo puede escribir cualquiera, la dirección no.
|
|
62
|
+
*/
|
|
63
|
+
export declare function x402Currency(token: string, network?: PanalNetwork): X402Currency | null;
|
|
64
|
+
/**
|
|
65
|
+
* Lo mismo, admitiendo también el símbolo: `USDC`, `gho`, `$PANAL` o `PANAL`.
|
|
66
|
+
* Es para configurar un agente a mano (`X402_TOKEN=USDC`), no para leer lo que
|
|
67
|
+
* llega de fuera: eso va siempre por dirección.
|
|
68
|
+
*/
|
|
69
|
+
export declare function x402CurrencyByName(name: string, network?: PanalNetwork): X402Currency | null;
|
|
70
|
+
/** Un importe en unidades mínimas, en texto legible: `0.05`. */
|
|
71
|
+
export declare function formatX402Amount(amount: bigint | string, currency: X402Currency): string;
|
|
72
|
+
/** `0.05` → unidades mínimas de esa moneda. Lanza si el texto no es un número. */
|
|
73
|
+
export declare function parseX402Amount(text: string, currency: X402Currency): bigint;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Las monedas con las que se paga una consulta x402 en Panal.
|
|
3
|
+
*
|
|
4
|
+
* POR QUÉ UNA LISTA CERRADA
|
|
5
|
+
*
|
|
6
|
+
* La cotización la escribe el agente: qué token, qué importe y cómo se llama.
|
|
7
|
+
* Fiarse de eso para enseñar el precio era el agujero. Un agente podía cobrar
|
|
8
|
+
* en un token de 6 decimales y llamarlo «$PANAL»: 1.000 USDC se pintaban
|
|
9
|
+
* «0,000000001 $PANAL» y la persona firmaba un permiso por 1.000 USDC creyendo
|
|
10
|
+
* que no pagaba nada.
|
|
11
|
+
*
|
|
12
|
+
* Aquí se fija, para cada red, qué tokens se aceptan y con qué nombre y
|
|
13
|
+
* decimales. El nombre y los decimales salen SIEMPRE de esta lista, nunca de
|
|
14
|
+
* la cotización, y un token que no está aquí no se paga. Es la misma lista
|
|
15
|
+
* para todas las capas —web, app, MCP y agentes— para que ninguna pueda
|
|
16
|
+
* enseñar un precio distinto del que se firma.
|
|
17
|
+
*
|
|
18
|
+
* Las cuatro de token cobran con `permit` (EIP-2612), comprobado en mainnet:
|
|
19
|
+
* firma gratis, y el gas lo pone el agente que cobra. MON, la moneda nativa,
|
|
20
|
+
* no tiene `permit`: quien paga MANDA una transferencia (paga él su gas, una
|
|
21
|
+
* fracción de céntimo) y la presenta. Ver `native-transfer` en x402-server.ts.
|
|
22
|
+
*
|
|
23
|
+
* - MON — la moneda de Monad. Nadie puede congelarla.
|
|
24
|
+
* - $PANAL — el token de Panal.
|
|
25
|
+
* - GHO — de Aave. Respaldo cripto, sin lista negra ni pausa: nadie puede
|
|
26
|
+
* congelarlo. La opción estable que no depende de una empresa.
|
|
27
|
+
* - USDC — de Circle. Respaldo en dólares; Circle puede congelar
|
|
28
|
+
* direcciones y pausar el token.
|
|
29
|
+
* - AUSD — de Agora. Respaldo en dólares; Agora puede congelar
|
|
30
|
+
* direcciones y pausar transferencias.
|
|
31
|
+
*
|
|
32
|
+
* En x402 que USDC y AUSD se puedan congelar pesa poco: Panal no guarda nada,
|
|
33
|
+
* el dinero va directo de quien paga al agente, y si alguien es congelado es
|
|
34
|
+
* en su propia wallet. Cada uno elige con qué paga o cobra.
|
|
35
|
+
*/
|
|
36
|
+
import { formatUnits, getAddress, parseUnits } from 'viem';
|
|
37
|
+
/** La moneda nativa, como la escribe Panal en todas partes: la dirección cero. */
|
|
38
|
+
export const X402_NATIVE = '0x0000000000000000000000000000000000000000';
|
|
39
|
+
const MON = {
|
|
40
|
+
symbol: 'MON',
|
|
41
|
+
address: X402_NATIVE,
|
|
42
|
+
decimals: 18,
|
|
43
|
+
scheme: 'native-transfer',
|
|
44
|
+
issuerCanFreeze: false,
|
|
45
|
+
};
|
|
46
|
+
const MAINNET = [
|
|
47
|
+
{
|
|
48
|
+
symbol: '$PANAL',
|
|
49
|
+
address: getAddress('0x2e2e44e7fa6178822d4397299f719e89d1a67777'),
|
|
50
|
+
decimals: 18,
|
|
51
|
+
scheme: 'eip2612-permit',
|
|
52
|
+
issuerCanFreeze: false,
|
|
53
|
+
},
|
|
54
|
+
MON,
|
|
55
|
+
{
|
|
56
|
+
symbol: 'GHO',
|
|
57
|
+
address: getAddress('0xfc421ad3c883bf9e7c4f42de845c4e4405799e73'),
|
|
58
|
+
decimals: 18,
|
|
59
|
+
scheme: 'eip2612-permit',
|
|
60
|
+
issuerCanFreeze: false,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
symbol: 'USDC',
|
|
64
|
+
address: getAddress('0x754704bc059f8c67012fed69bc8a327a5aafb603'),
|
|
65
|
+
decimals: 6,
|
|
66
|
+
scheme: 'eip2612-permit',
|
|
67
|
+
issuerCanFreeze: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
symbol: 'AUSD',
|
|
71
|
+
address: getAddress('0x00000000efe302beaa2b3e6e1b18d08d69a9012a'),
|
|
72
|
+
decimals: 6,
|
|
73
|
+
scheme: 'eip2612-permit',
|
|
74
|
+
issuerCanFreeze: true,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
/** En testnet no hay ninguno de estos tokens, pero MON sí. */
|
|
78
|
+
const TESTNET = [MON];
|
|
79
|
+
/** Las monedas aceptadas en una red, en el orden en que se ofrecen. */
|
|
80
|
+
export function x402Currencies(network = 'mainnet') {
|
|
81
|
+
return network === 'mainnet' ? MAINNET : TESTNET;
|
|
82
|
+
}
|
|
83
|
+
/** La red de un chainId de Monad, o `null` si no es ninguna de las dos. */
|
|
84
|
+
export function networkOfChain(chainId) {
|
|
85
|
+
return chainId === 143 ? 'mainnet' : chainId === 10143 ? 'testnet' : null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* La moneda de un token, o `null` si Panal no acepta pagar en él.
|
|
89
|
+
*
|
|
90
|
+
* Por dirección: el nombre lo puede escribir cualquiera, la dirección no.
|
|
91
|
+
*/
|
|
92
|
+
export function x402Currency(token, network = 'mainnet') {
|
|
93
|
+
const t = token.trim().toLowerCase();
|
|
94
|
+
return x402Currencies(network).find((c) => c.address.toLowerCase() === t) ?? null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Lo mismo, admitiendo también el símbolo: `USDC`, `gho`, `$PANAL` o `PANAL`.
|
|
98
|
+
* Es para configurar un agente a mano (`X402_TOKEN=USDC`), no para leer lo que
|
|
99
|
+
* llega de fuera: eso va siempre por dirección.
|
|
100
|
+
*/
|
|
101
|
+
export function x402CurrencyByName(name, network = 'mainnet') {
|
|
102
|
+
const n = name.trim().replace(/^\$/, '').toUpperCase();
|
|
103
|
+
return x402Currencies(network).find((c) => c.symbol.replace(/^\$/, '').toUpperCase() === n) ?? x402Currency(name, network);
|
|
104
|
+
}
|
|
105
|
+
/** Un importe en unidades mínimas, en texto legible: `0.05`. */
|
|
106
|
+
export function formatX402Amount(amount, currency) {
|
|
107
|
+
return formatUnits(BigInt(amount), currency.decimals);
|
|
108
|
+
}
|
|
109
|
+
/** `0.05` → unidades mínimas de esa moneda. Lanza si el texto no es un número. */
|
|
110
|
+
export function parseX402Amount(text, currency) {
|
|
111
|
+
return parseUnits(text.trim(), currency.decimals);
|
|
112
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,14 +27,16 @@ export { TaskStatus, TASK_STATUS_LABEL, formatAgentMetadata, normalizeAgentLink,
|
|
|
27
27
|
export type { Agent, AgentLinkKey, AgentLinks, AgentMetadata, Task } from './types.js';
|
|
28
28
|
export { erc20Abi, escrowAbi, registryAbi } from './abis.js';
|
|
29
29
|
export { X402_SCHEME, X402Error, payAndAsk, quoteAsk } from './x402.js';
|
|
30
|
+
export { X402_NATIVE, formatX402Amount, networkOfChain, parseX402Amount, x402Currencies, x402Currency, x402CurrencyByName, } from './currencies.js';
|
|
31
|
+
export type { X402Currency, X402Scheme } from './currencies.js';
|
|
30
32
|
export type { AskResult, PayAndAskOptions, PermitDomain, X402Accept, X402Quote } from './x402.js';
|
|
31
33
|
export { assertPublicUrl, fetchBytesLimited, fetchLimited, isPrivateIp } from './net.js';
|
|
32
34
|
export { ATTACH_BLOCK, FILES_BLOCK, MAX_FILE_BYTES, FileVerificationError, appendAttachmentsManifest, appendFilesManifest, attachmentFrom, buildAttachmentsManifest, buildFilesManifest, downloadDeliveredFile, fileUrl, matchAttachment, parseAttachmentsManifest, parseFilesManifest, sanitizeFileName, stripFilesManifest, verifyFileBytes, } from './files.js';
|
|
33
35
|
export type { AttachedFile, DeliveredFile, DownloadOptions, HashedFile } from './files.js';
|
|
34
36
|
export { MAX_IMAGEN_BYTES, MIMES_IMAGEN, PROVEEDORES, LlmError, dialectoDe, esImagenSoportada, llmChat, resolverLlm, } from './llm.js';
|
|
35
37
|
export type { LlmConfig, LlmDialecto, LlmImagen, LlmPeticion, LlmProveedor } from './llm.js';
|
|
36
|
-
export { X402_VERSION, X402_SERVER_SCHEME, buildQuote, enqueueByPayer, parsePaymentHeader, permitNonce, permitTypedData, readPermitDomain, resourceId, splitSignature, verifyAndSettle, } from './x402-server.js';
|
|
37
|
-
export type { SettleDeps, SettleResult, X402Payment, X402ServerAccept, X402ServerQuote, } from './x402-server.js';
|
|
38
|
+
export { X402_VERSION, X402_SERVER_SCHEME, X402_NATIVE_SCHEME, buildNativeQuote, buildQuote, enqueueByPayer, newQuoteSecret, parsePaymentHeader, parseX402Header, permitNonce, permitTypedData, readPermitDomain, resourceId, splitSignature, verifyAndSettle, verifyNativePayment, } from './x402-server.js';
|
|
39
|
+
export type { NativeReplayGuard, SettleDeps, SettleResult, X402NativeAccept, X402NativePayment, X402Payment, X402ServerAccept, X402ServerQuote, } from './x402-server.js';
|
|
38
40
|
export { ENVELOPE_HEADERS, DEFAULT_DEPTH, MAX_DEPTH, BudgetExhausted, DepthExhausted, LoopDetected, assertCanServe, descend, envelopeHeaders, newEnvelope, parseEnvelope, remainingBudget, } from './envelope.js';
|
|
39
41
|
export type { CallEnvelope } from './envelope.js';
|
|
40
42
|
export type { UrlGuardOptions } from './net.js';
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export { TaskStatus, TASK_STATUS_LABEL, formatAgentMetadata, normalizeAgentLink,
|
|
|
25
25
|
export { erc20Abi, escrowAbi, registryAbi } from './abis.js';
|
|
26
26
|
// x402: pagar a otro agente por una consulta, sin escrow y sin humano.
|
|
27
27
|
export { X402_SCHEME, X402Error, payAndAsk, quoteAsk } from './x402.js';
|
|
28
|
+
export { X402_NATIVE, formatX402Amount, networkOfChain, parseX402Amount, x402Currencies, x402Currency, x402CurrencyByName, } from './currencies.js';
|
|
28
29
|
export { assertPublicUrl, fetchBytesLimited, fetchLimited, isPrivateIp } from './net.js';
|
|
29
30
|
// Archivos: entregar un PDF o un vídeo anclando SU hash, no el del enlace.
|
|
30
31
|
// Y en la otra dirección, adjuntar una foto al encargo con la misma garantía.
|
|
@@ -34,7 +35,7 @@ export { ATTACH_BLOCK, FILES_BLOCK, MAX_FILE_BYTES, FileVerificationError, appen
|
|
|
34
35
|
export { MAX_IMAGEN_BYTES, MIMES_IMAGEN, PROVEEDORES, LlmError, dialectoDe, esImagenSoportada, llmChat, resolverLlm, } from './llm.js';
|
|
35
36
|
// x402: la otra mitad, cobrar por llamada. Portada del bot de LexPanal, donde
|
|
36
37
|
// lleva meses cobrando en produccion.
|
|
37
|
-
export { X402_VERSION, X402_SERVER_SCHEME, buildQuote, enqueueByPayer, parsePaymentHeader, permitNonce, permitTypedData, readPermitDomain, resourceId, splitSignature, verifyAndSettle, } from './x402-server.js';
|
|
38
|
+
export { X402_VERSION, X402_SERVER_SCHEME, X402_NATIVE_SCHEME, buildNativeQuote, buildQuote, enqueueByPayer, newQuoteSecret, parsePaymentHeader, parseX402Header, permitNonce, permitTypedData, readPermitDomain, resourceId, splitSignature, verifyAndSettle, verifyNativePayment, } from './x402-server.js';
|
|
38
39
|
// El sobre que viaja entre agentes: profundidad, presupuesto y detección de ciclos.
|
|
39
40
|
export { ENVELOPE_HEADERS, DEFAULT_DEPTH, MAX_DEPTH, BudgetExhausted, DepthExhausted, LoopDetected, assertCanServe, descend, envelopeHeaders, newEnvelope, parseEnvelope, remainingBudget, } from './envelope.js';
|
|
40
41
|
// La ficha de GET /agent.json: un solo formato, y lectores que perdonan el
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|