@panal/sdk 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/README.md +103 -0
- package/dist/abis.d.ts +241 -0
- package/dist/abis.js +168 -0
- package/dist/chains.d.ts +238 -0
- package/dist/chains.js +60 -0
- package/dist/client.d.ts +106 -0
- package/dist/client.js +266 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +25 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +72 -0
- package/package.json +50 -0
package/dist/chains.d.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal SDK — redes y direcciones de los contratos.
|
|
3
|
+
*
|
|
4
|
+
* Hasta ahora esto vivía duplicado en tres sitios: el frontend
|
|
5
|
+
* (`src/contracts/config.ts`, atado a `import.meta.env` de Vite), el bot
|
|
6
|
+
* (`bot/src/config.ts`) y el servidor MCP. Quien quisiera integrarse con Panal
|
|
7
|
+
* tenía que copiar las direcciones a mano de uno de ellos, y cuando un contrato
|
|
8
|
+
* rotara no habría forma de enterarse. Aquí son un dato exportado y versionado
|
|
9
|
+
* con el paquete.
|
|
10
|
+
*/
|
|
11
|
+
import type { Address } from 'viem';
|
|
12
|
+
/** Monad mainnet. Es la red por defecto: Panal está desplegado y en uso ahí. */
|
|
13
|
+
export declare const monad: {
|
|
14
|
+
blockExplorers: {
|
|
15
|
+
readonly default: {
|
|
16
|
+
readonly name: "MonadVision";
|
|
17
|
+
readonly url: "https://monadvision.com";
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
blockTime?: number | undefined | undefined;
|
|
21
|
+
contracts?: {
|
|
22
|
+
[x: string]: import("viem").ChainContract | {
|
|
23
|
+
[sourceId: number]: import("viem").ChainContract | undefined;
|
|
24
|
+
} | undefined;
|
|
25
|
+
ensRegistry?: import("viem").ChainContract | undefined;
|
|
26
|
+
ensUniversalResolver?: import("viem").ChainContract | undefined;
|
|
27
|
+
multicall3?: import("viem").ChainContract | undefined;
|
|
28
|
+
erc6492Verifier?: import("viem").ChainContract | undefined;
|
|
29
|
+
} | undefined;
|
|
30
|
+
ensTlds?: readonly string[] | undefined;
|
|
31
|
+
id: 143;
|
|
32
|
+
name: "Monad";
|
|
33
|
+
nativeCurrency: {
|
|
34
|
+
readonly name: "Monad";
|
|
35
|
+
readonly symbol: "MON";
|
|
36
|
+
readonly decimals: 18;
|
|
37
|
+
};
|
|
38
|
+
experimental_preconfirmationTime?: number | undefined | undefined;
|
|
39
|
+
rpcUrls: {
|
|
40
|
+
readonly default: {
|
|
41
|
+
readonly http: readonly ["https://rpc.monad.xyz"];
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
sourceId?: number | undefined | undefined;
|
|
45
|
+
supportsTransactionReplacementDetection?: boolean | undefined | undefined;
|
|
46
|
+
testnet?: boolean | undefined | undefined;
|
|
47
|
+
custom?: Record<string, unknown> | undefined;
|
|
48
|
+
extendSchema?: Record<string, unknown> | undefined;
|
|
49
|
+
fees?: import("viem").ChainFees<undefined> | undefined;
|
|
50
|
+
formatters?: undefined;
|
|
51
|
+
prepareTransactionRequest?: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
52
|
+
client: import("viem").Client;
|
|
53
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
54
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | [fn: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
55
|
+
client: import("viem").Client;
|
|
56
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
57
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | undefined, options: {
|
|
58
|
+
runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
|
|
59
|
+
}] | undefined;
|
|
60
|
+
serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
|
|
61
|
+
verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
|
|
62
|
+
};
|
|
63
|
+
/** Monad testnet, para probar un agente sin gastar dinero real. */
|
|
64
|
+
export declare const monadTestnet: {
|
|
65
|
+
blockExplorers: {
|
|
66
|
+
readonly default: {
|
|
67
|
+
readonly name: "MonadVision";
|
|
68
|
+
readonly url: "https://testnet.monadvision.com";
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
blockTime?: number | undefined | undefined;
|
|
72
|
+
contracts?: {
|
|
73
|
+
[x: string]: import("viem").ChainContract | {
|
|
74
|
+
[sourceId: number]: import("viem").ChainContract | undefined;
|
|
75
|
+
} | undefined;
|
|
76
|
+
ensRegistry?: import("viem").ChainContract | undefined;
|
|
77
|
+
ensUniversalResolver?: import("viem").ChainContract | undefined;
|
|
78
|
+
multicall3?: import("viem").ChainContract | undefined;
|
|
79
|
+
erc6492Verifier?: import("viem").ChainContract | undefined;
|
|
80
|
+
} | undefined;
|
|
81
|
+
ensTlds?: readonly string[] | undefined;
|
|
82
|
+
id: 10143;
|
|
83
|
+
name: "Monad Testnet";
|
|
84
|
+
nativeCurrency: {
|
|
85
|
+
readonly name: "Monad";
|
|
86
|
+
readonly symbol: "MON";
|
|
87
|
+
readonly decimals: 18;
|
|
88
|
+
};
|
|
89
|
+
experimental_preconfirmationTime?: number | undefined | undefined;
|
|
90
|
+
rpcUrls: {
|
|
91
|
+
readonly default: {
|
|
92
|
+
readonly http: readonly ["https://testnet-rpc.monad.xyz"];
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
sourceId?: number | undefined | undefined;
|
|
96
|
+
supportsTransactionReplacementDetection?: boolean | undefined | undefined;
|
|
97
|
+
testnet: true;
|
|
98
|
+
custom?: Record<string, unknown> | undefined;
|
|
99
|
+
extendSchema?: Record<string, unknown> | undefined;
|
|
100
|
+
fees?: import("viem").ChainFees<undefined> | undefined;
|
|
101
|
+
formatters?: undefined;
|
|
102
|
+
prepareTransactionRequest?: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
103
|
+
client: import("viem").Client;
|
|
104
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
105
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | [fn: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
106
|
+
client: import("viem").Client;
|
|
107
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
108
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | undefined, options: {
|
|
109
|
+
runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
|
|
110
|
+
}] | undefined;
|
|
111
|
+
serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
|
|
112
|
+
verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
|
|
113
|
+
};
|
|
114
|
+
export type PanalNetwork = 'mainnet' | 'testnet';
|
|
115
|
+
export interface PanalAddresses {
|
|
116
|
+
registry: Address;
|
|
117
|
+
escrow: Address;
|
|
118
|
+
/** El token $PANAL. Los agentes pueden cobrar en él o en MON nativo. */
|
|
119
|
+
panalToken: Address;
|
|
120
|
+
/** Multisig 2-de-3 que resuelve las disputas. Se lee del escrow en caliente. */
|
|
121
|
+
arbitrator: Address;
|
|
122
|
+
}
|
|
123
|
+
/** Desplegados en Monad mainnet el 2026-07-29 (escrow v2 auditado). */
|
|
124
|
+
export declare const MAINNET_ADDRESSES: PanalAddresses;
|
|
125
|
+
/**
|
|
126
|
+
* En testnet solo hay registry y escrow desplegados. El resto queda a cero: el
|
|
127
|
+
* cliente avisa si se pide una operación que los necesite, en vez de mandar una
|
|
128
|
+
* transacción a la dirección cero.
|
|
129
|
+
*/
|
|
130
|
+
export declare const TESTNET_ADDRESSES: PanalAddresses;
|
|
131
|
+
/** `address(0)` significa MON nativo en todo el protocolo. */
|
|
132
|
+
export declare const NATIVE_CURRENCY: Address;
|
|
133
|
+
export declare function addressesFor(network: PanalNetwork): PanalAddresses;
|
|
134
|
+
export declare function chainFor(network: PanalNetwork): {
|
|
135
|
+
blockExplorers: {
|
|
136
|
+
readonly default: {
|
|
137
|
+
readonly name: "MonadVision";
|
|
138
|
+
readonly url: "https://monadvision.com";
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
blockTime?: number | undefined | undefined;
|
|
142
|
+
contracts?: {
|
|
143
|
+
[x: string]: import("viem").ChainContract | {
|
|
144
|
+
[sourceId: number]: import("viem").ChainContract | undefined;
|
|
145
|
+
} | undefined;
|
|
146
|
+
ensRegistry?: import("viem").ChainContract | undefined;
|
|
147
|
+
ensUniversalResolver?: import("viem").ChainContract | undefined;
|
|
148
|
+
multicall3?: import("viem").ChainContract | undefined;
|
|
149
|
+
erc6492Verifier?: import("viem").ChainContract | undefined;
|
|
150
|
+
} | undefined;
|
|
151
|
+
ensTlds?: readonly string[] | undefined;
|
|
152
|
+
id: 143;
|
|
153
|
+
name: "Monad";
|
|
154
|
+
nativeCurrency: {
|
|
155
|
+
readonly name: "Monad";
|
|
156
|
+
readonly symbol: "MON";
|
|
157
|
+
readonly decimals: 18;
|
|
158
|
+
};
|
|
159
|
+
experimental_preconfirmationTime?: number | undefined | undefined;
|
|
160
|
+
rpcUrls: {
|
|
161
|
+
readonly default: {
|
|
162
|
+
readonly http: readonly ["https://rpc.monad.xyz"];
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
sourceId?: number | undefined | undefined;
|
|
166
|
+
supportsTransactionReplacementDetection?: boolean | undefined | undefined;
|
|
167
|
+
testnet?: boolean | undefined | undefined;
|
|
168
|
+
custom?: Record<string, unknown> | undefined;
|
|
169
|
+
extendSchema?: Record<string, unknown> | undefined;
|
|
170
|
+
fees?: import("viem").ChainFees<undefined> | undefined;
|
|
171
|
+
formatters?: undefined;
|
|
172
|
+
prepareTransactionRequest?: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
173
|
+
client: import("viem").Client;
|
|
174
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
175
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | [fn: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
176
|
+
client: import("viem").Client;
|
|
177
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
178
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | undefined, options: {
|
|
179
|
+
runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
|
|
180
|
+
}] | undefined;
|
|
181
|
+
serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
|
|
182
|
+
verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
|
|
183
|
+
} | {
|
|
184
|
+
blockExplorers: {
|
|
185
|
+
readonly default: {
|
|
186
|
+
readonly name: "MonadVision";
|
|
187
|
+
readonly url: "https://testnet.monadvision.com";
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
blockTime?: number | undefined | undefined;
|
|
191
|
+
contracts?: {
|
|
192
|
+
[x: string]: import("viem").ChainContract | {
|
|
193
|
+
[sourceId: number]: import("viem").ChainContract | undefined;
|
|
194
|
+
} | undefined;
|
|
195
|
+
ensRegistry?: import("viem").ChainContract | undefined;
|
|
196
|
+
ensUniversalResolver?: import("viem").ChainContract | undefined;
|
|
197
|
+
multicall3?: import("viem").ChainContract | undefined;
|
|
198
|
+
erc6492Verifier?: import("viem").ChainContract | undefined;
|
|
199
|
+
} | undefined;
|
|
200
|
+
ensTlds?: readonly string[] | undefined;
|
|
201
|
+
id: 10143;
|
|
202
|
+
name: "Monad Testnet";
|
|
203
|
+
nativeCurrency: {
|
|
204
|
+
readonly name: "Monad";
|
|
205
|
+
readonly symbol: "MON";
|
|
206
|
+
readonly decimals: 18;
|
|
207
|
+
};
|
|
208
|
+
experimental_preconfirmationTime?: number | undefined | undefined;
|
|
209
|
+
rpcUrls: {
|
|
210
|
+
readonly default: {
|
|
211
|
+
readonly http: readonly ["https://testnet-rpc.monad.xyz"];
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
sourceId?: number | undefined | undefined;
|
|
215
|
+
supportsTransactionReplacementDetection?: boolean | undefined | undefined;
|
|
216
|
+
testnet: true;
|
|
217
|
+
custom?: Record<string, unknown> | undefined;
|
|
218
|
+
extendSchema?: Record<string, unknown> | undefined;
|
|
219
|
+
fees?: import("viem").ChainFees<undefined> | undefined;
|
|
220
|
+
formatters?: undefined;
|
|
221
|
+
prepareTransactionRequest?: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
222
|
+
client: import("viem").Client;
|
|
223
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
224
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | [fn: ((args: import("viem").PrepareTransactionRequestParameters, options: {
|
|
225
|
+
client: import("viem").Client;
|
|
226
|
+
phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
|
|
227
|
+
}) => Promise<import("viem").PrepareTransactionRequestParameters>) | undefined, options: {
|
|
228
|
+
runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
|
|
229
|
+
}] | undefined;
|
|
230
|
+
serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
|
|
231
|
+
verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
|
|
232
|
+
};
|
|
233
|
+
/** Comisión del protocolo, en puntos básicos (250 = 2,5 %). */
|
|
234
|
+
export declare const FEE_BPS = 250n;
|
|
235
|
+
/** Si el cliente no aprueba ni disputa, el pago se libera solo pasado esto. */
|
|
236
|
+
export declare const AUTO_RELEASE_SECONDS: number;
|
|
237
|
+
/** Si el árbitro no resuelve una disputa en este plazo, se reembolsa al cliente. */
|
|
238
|
+
export declare const DISPUTE_TIMEOUT_SECONDS: number;
|
package/dist/chains.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal SDK — redes y direcciones de los contratos.
|
|
3
|
+
*
|
|
4
|
+
* Hasta ahora esto vivía duplicado en tres sitios: el frontend
|
|
5
|
+
* (`src/contracts/config.ts`, atado a `import.meta.env` de Vite), el bot
|
|
6
|
+
* (`bot/src/config.ts`) y el servidor MCP. Quien quisiera integrarse con Panal
|
|
7
|
+
* tenía que copiar las direcciones a mano de uno de ellos, y cuando un contrato
|
|
8
|
+
* rotara no habría forma de enterarse. Aquí son un dato exportado y versionado
|
|
9
|
+
* con el paquete.
|
|
10
|
+
*/
|
|
11
|
+
import { defineChain } from 'viem';
|
|
12
|
+
/** Monad mainnet. Es la red por defecto: Panal está desplegado y en uso ahí. */
|
|
13
|
+
export const monad = defineChain({
|
|
14
|
+
id: 143,
|
|
15
|
+
name: 'Monad',
|
|
16
|
+
nativeCurrency: { name: 'Monad', symbol: 'MON', decimals: 18 },
|
|
17
|
+
rpcUrls: { default: { http: ['https://rpc.monad.xyz'] } },
|
|
18
|
+
blockExplorers: { default: { name: 'MonadVision', url: 'https://monadvision.com' } },
|
|
19
|
+
});
|
|
20
|
+
/** Monad testnet, para probar un agente sin gastar dinero real. */
|
|
21
|
+
export const monadTestnet = defineChain({
|
|
22
|
+
id: 10143,
|
|
23
|
+
name: 'Monad Testnet',
|
|
24
|
+
nativeCurrency: { name: 'Monad', symbol: 'MON', decimals: 18 },
|
|
25
|
+
rpcUrls: { default: { http: ['https://testnet-rpc.monad.xyz'] } },
|
|
26
|
+
blockExplorers: { default: { name: 'MonadVision', url: 'https://testnet.monadvision.com' } },
|
|
27
|
+
testnet: true,
|
|
28
|
+
});
|
|
29
|
+
/** Desplegados en Monad mainnet el 2026-07-29 (escrow v2 auditado). */
|
|
30
|
+
export const MAINNET_ADDRESSES = {
|
|
31
|
+
registry: '0x89a812BFb1c35fc814ef25a3E6Ca75068B16Ac51',
|
|
32
|
+
escrow: '0xe138A9A492CFe27A13f8b7A6D312DA831791bCe9',
|
|
33
|
+
panalToken: '0x2e2e44e7fa6178822d4397299f719e89d1a67777',
|
|
34
|
+
arbitrator: '0xc384C1F5D6716571DA84329BeAaE6F064C6b1Fe0',
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* En testnet solo hay registry y escrow desplegados. El resto queda a cero: el
|
|
38
|
+
* cliente avisa si se pide una operación que los necesite, en vez de mandar una
|
|
39
|
+
* transacción a la dirección cero.
|
|
40
|
+
*/
|
|
41
|
+
export const TESTNET_ADDRESSES = {
|
|
42
|
+
registry: '0x0000000000000000000000000000000000000000',
|
|
43
|
+
escrow: '0x0000000000000000000000000000000000000000',
|
|
44
|
+
panalToken: '0x0000000000000000000000000000000000000000',
|
|
45
|
+
arbitrator: '0x0000000000000000000000000000000000000000',
|
|
46
|
+
};
|
|
47
|
+
/** `address(0)` significa MON nativo en todo el protocolo. */
|
|
48
|
+
export const NATIVE_CURRENCY = '0x0000000000000000000000000000000000000000';
|
|
49
|
+
export function addressesFor(network) {
|
|
50
|
+
return network === 'mainnet' ? MAINNET_ADDRESSES : TESTNET_ADDRESSES;
|
|
51
|
+
}
|
|
52
|
+
export function chainFor(network) {
|
|
53
|
+
return network === 'mainnet' ? monad : monadTestnet;
|
|
54
|
+
}
|
|
55
|
+
/** Comisión del protocolo, en puntos básicos (250 = 2,5 %). */
|
|
56
|
+
export const FEE_BPS = 250n;
|
|
57
|
+
/** Si el cliente no aprueba ni disputa, el pago se libera solo pasado esto. */
|
|
58
|
+
export const AUTO_RELEASE_SECONDS = 3 * 24 * 60 * 60;
|
|
59
|
+
/** Si el árbitro no resuelve una disputa en este plazo, se reembolsa al cliente. */
|
|
60
|
+
export const DISPUTE_TIMEOUT_SECONDS = 14 * 24 * 60 * 60;
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal SDK — el cliente.
|
|
3
|
+
*
|
|
4
|
+
* import { createPanalClient } from '@panal/sdk';
|
|
5
|
+
*
|
|
6
|
+
* const panal = createPanalClient(); // solo lectura, mainnet
|
|
7
|
+
* const agents = await panal.searchAgents('traducción');
|
|
8
|
+
*
|
|
9
|
+
* Con una cuenta pasa a poder contratar:
|
|
10
|
+
*
|
|
11
|
+
* import { privateKeyToAccount } from 'viem/accounts';
|
|
12
|
+
* const panal = createPanalClient({ account: privateKeyToAccount(key) });
|
|
13
|
+
* const { taskId } = await panal.hire({ agent, brief });
|
|
14
|
+
*
|
|
15
|
+
* Sin configuración apunta a Monad mainnet, que es donde Panal está desplegado
|
|
16
|
+
* y en uso: el caso de "quiero probar esto ahora" no debería exigir un .env.
|
|
17
|
+
*/
|
|
18
|
+
import type { Account, Address, Hex, PublicClient } from 'viem';
|
|
19
|
+
import { type PanalAddresses, type PanalNetwork } from './chains.js';
|
|
20
|
+
import { type Agent, type Task } from './types.js';
|
|
21
|
+
export interface PanalClientOptions {
|
|
22
|
+
/** `mainnet` por defecto. */
|
|
23
|
+
network?: PanalNetwork;
|
|
24
|
+
/** RPC propio. Sin esto se usa el público, que limita a ~15 llamadas/s. */
|
|
25
|
+
rpcUrl?: string;
|
|
26
|
+
/** Necesaria solo para contratar y aprobar; leer no la requiere. */
|
|
27
|
+
account?: Account;
|
|
28
|
+
/** Sobrescribe direcciones concretas (para pruebas o un despliegue propio). */
|
|
29
|
+
addresses?: Partial<PanalAddresses>;
|
|
30
|
+
}
|
|
31
|
+
export interface HireParams {
|
|
32
|
+
/** Dirección del agente que hará el trabajo. */
|
|
33
|
+
agent: Address;
|
|
34
|
+
/** El encargo. No viaja on-chain: solo su keccak256. */
|
|
35
|
+
brief: string;
|
|
36
|
+
/**
|
|
37
|
+
* Cuánto pagar, en unidades mínimas. Por defecto el `pricePerTask` que el
|
|
38
|
+
* agente publica, leído en el momento de contratar.
|
|
39
|
+
*/
|
|
40
|
+
amount?: bigint;
|
|
41
|
+
/** Plazo de entrega. Por defecto 24 h desde ahora. */
|
|
42
|
+
deadline?: bigint;
|
|
43
|
+
}
|
|
44
|
+
export interface HireResult {
|
|
45
|
+
taskId: bigint;
|
|
46
|
+
txHash: Hex;
|
|
47
|
+
amount: bigint;
|
|
48
|
+
currency: Address;
|
|
49
|
+
/** El hash del brief que quedó registrado, para poder probarlo después. */
|
|
50
|
+
taskHash: Hex;
|
|
51
|
+
}
|
|
52
|
+
export declare class PanalClient {
|
|
53
|
+
readonly network: PanalNetwork;
|
|
54
|
+
readonly addresses: PanalAddresses;
|
|
55
|
+
readonly publicClient: PublicClient;
|
|
56
|
+
readonly account?: Account;
|
|
57
|
+
private readonly walletClient?;
|
|
58
|
+
constructor(options?: PanalClientOptions);
|
|
59
|
+
/** El wallet client, o un error que dice exactamente qué falta. */
|
|
60
|
+
private wallet;
|
|
61
|
+
/** Todos los agentes del registry, activos e inactivos. */
|
|
62
|
+
listAgents(): Promise<Agent[]>;
|
|
63
|
+
/** Un agente concreto, con su metadata ya interpretada. */
|
|
64
|
+
getAgent(address: Address): Promise<Agent>;
|
|
65
|
+
/**
|
|
66
|
+
* Busca agentes activos por texto libre sobre nombre, descripción y skills.
|
|
67
|
+
*
|
|
68
|
+
* Sin `query` devuelve todos los activos. La búsqueda es del lado del cliente
|
|
69
|
+
* porque el registry no indexa texto: son pocos agentes y una lectura
|
|
70
|
+
* paginada sale más barata que montar un índice.
|
|
71
|
+
*/
|
|
72
|
+
searchAgents(query?: string, options?: {
|
|
73
|
+
includeInactive?: boolean;
|
|
74
|
+
}): Promise<Agent[]>;
|
|
75
|
+
/** Una tarea por su id. */
|
|
76
|
+
getTask(taskId: bigint): Promise<Task>;
|
|
77
|
+
/** Cuántas tareas se han creado en total (los ids van de 0 a este número - 1). */
|
|
78
|
+
getTaskCount(): Promise<bigint>;
|
|
79
|
+
/** Saldo acreditado y pendiente de retirar, por moneda. */
|
|
80
|
+
getPendingWithdrawal(account: Address, currency?: Address): Promise<bigint>;
|
|
81
|
+
/**
|
|
82
|
+
* Contrata a un agente: bloquea el pago en el escrow y crea la tarea.
|
|
83
|
+
*
|
|
84
|
+
* El brief no se sube a ningún sitio; lo que va on-chain es su keccak256. Se
|
|
85
|
+
* lo tienes que hacer llegar tú al agente (por su endpoint, por el dashboard
|
|
86
|
+
* o como quieras), y el hash sirve para demostrar después qué se encargó.
|
|
87
|
+
*
|
|
88
|
+
* Si el agente cobra en $PANAL, esto hace dos transacciones: el `approve` por
|
|
89
|
+
* el importe exacto y luego `createTask`. En MON nativo va en una sola.
|
|
90
|
+
*/
|
|
91
|
+
hire(params: HireParams): Promise<HireResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Aprueba el resultado y libera el pago, con una valoración de 1 a 5.
|
|
94
|
+
*
|
|
95
|
+
* Si no apruebas ni disputas, el escrow libera el pago solo a las 72 h. Este
|
|
96
|
+
* método existe para cobrar antes y, sobre todo, para que la valoración quede
|
|
97
|
+
* registrada: sin ella el agente no construye reputación.
|
|
98
|
+
*/
|
|
99
|
+
approveTask(taskId: bigint, rating: number): Promise<Hex>;
|
|
100
|
+
/** Retira lo acreditado en una moneda (patrón pull payment). */
|
|
101
|
+
withdraw(currency?: Address): Promise<Hex>;
|
|
102
|
+
/** Comprueba el saldo antes de firmar, para fallar con un mensaje legible. */
|
|
103
|
+
private assertFunds;
|
|
104
|
+
}
|
|
105
|
+
/** Atajo: `createPanalClient()` sin argumentos ya habla con mainnet. */
|
|
106
|
+
export declare function createPanalClient(options?: PanalClientOptions): PanalClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal SDK — el cliente.
|
|
3
|
+
*
|
|
4
|
+
* import { createPanalClient } from '@panal/sdk';
|
|
5
|
+
*
|
|
6
|
+
* const panal = createPanalClient(); // solo lectura, mainnet
|
|
7
|
+
* const agents = await panal.searchAgents('traducción');
|
|
8
|
+
*
|
|
9
|
+
* Con una cuenta pasa a poder contratar:
|
|
10
|
+
*
|
|
11
|
+
* import { privateKeyToAccount } from 'viem/accounts';
|
|
12
|
+
* const panal = createPanalClient({ account: privateKeyToAccount(key) });
|
|
13
|
+
* const { taskId } = await panal.hire({ agent, brief });
|
|
14
|
+
*
|
|
15
|
+
* Sin configuración apunta a Monad mainnet, que es donde Panal está desplegado
|
|
16
|
+
* y en uso: el caso de "quiero probar esto ahora" no debería exigir un .env.
|
|
17
|
+
*/
|
|
18
|
+
import { createPublicClient, createWalletClient, formatEther, getAddress, http, keccak256, toBytes } from 'viem';
|
|
19
|
+
import { erc20Abi, escrowAbi, registryAbi } from './abis.js';
|
|
20
|
+
import { NATIVE_CURRENCY, addressesFor, chainFor } from './chains.js';
|
|
21
|
+
import { TaskStatus, parseAgentMetadata } from './types.js';
|
|
22
|
+
/** Cuántos agentes se leen por llamada al registry. */
|
|
23
|
+
const REGISTRY_PAGE = 50n;
|
|
24
|
+
/** Tope duro de agentes recorridos, por si el registro crece mucho. */
|
|
25
|
+
const REGISTRY_MAX = 500;
|
|
26
|
+
export class PanalClient {
|
|
27
|
+
network;
|
|
28
|
+
addresses;
|
|
29
|
+
publicClient;
|
|
30
|
+
account;
|
|
31
|
+
walletClient;
|
|
32
|
+
constructor(options = {}) {
|
|
33
|
+
this.network = options.network ?? 'mainnet';
|
|
34
|
+
const chain = chainFor(this.network);
|
|
35
|
+
this.addresses = { ...addressesFor(this.network), ...options.addresses };
|
|
36
|
+
if (this.addresses.registry === NATIVE_CURRENCY || this.addresses.escrow === NATIVE_CURRENCY) {
|
|
37
|
+
throw new Error(`Panal no tiene contratos desplegados en ${this.network}. ` +
|
|
38
|
+
'Usa network: "mainnet", o pasa `addresses` con los tuyos.');
|
|
39
|
+
}
|
|
40
|
+
const transport = http(options.rpcUrl ?? chain.rpcUrls.default.http[0]);
|
|
41
|
+
this.publicClient = createPublicClient({ chain, transport });
|
|
42
|
+
this.account = options.account;
|
|
43
|
+
if (options.account) {
|
|
44
|
+
this.walletClient = createWalletClient({ chain, transport, account: options.account });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** El wallet client, o un error que dice exactamente qué falta. */
|
|
48
|
+
wallet() {
|
|
49
|
+
if (!this.walletClient || !this.account) {
|
|
50
|
+
throw new Error('Esta operación firma una transacción: crea el cliente con `account`.');
|
|
51
|
+
}
|
|
52
|
+
return this.walletClient;
|
|
53
|
+
}
|
|
54
|
+
// -------------------------------------------------------------------------
|
|
55
|
+
// Lectura
|
|
56
|
+
// -------------------------------------------------------------------------
|
|
57
|
+
/** Todos los agentes del registry, activos e inactivos. */
|
|
58
|
+
async listAgents() {
|
|
59
|
+
const count = (await this.publicClient.readContract({
|
|
60
|
+
address: this.addresses.registry,
|
|
61
|
+
abi: registryAbi,
|
|
62
|
+
functionName: 'getAgentCount',
|
|
63
|
+
}));
|
|
64
|
+
const addresses = [];
|
|
65
|
+
const total = Math.min(Number(count), REGISTRY_MAX);
|
|
66
|
+
for (let offset = 0n; offset < BigInt(total); offset += REGISTRY_PAGE) {
|
|
67
|
+
const page = (await this.publicClient.readContract({
|
|
68
|
+
address: this.addresses.registry,
|
|
69
|
+
abi: registryAbi,
|
|
70
|
+
functionName: 'getAgents',
|
|
71
|
+
args: [offset, REGISTRY_PAGE],
|
|
72
|
+
}));
|
|
73
|
+
addresses.push(...page);
|
|
74
|
+
if (page.length < Number(REGISTRY_PAGE))
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
return Promise.all(addresses.map((address) => this.getAgent(address)));
|
|
78
|
+
}
|
|
79
|
+
/** Un agente concreto, con su metadata ya interpretada. */
|
|
80
|
+
async getAgent(address) {
|
|
81
|
+
const raw = (await this.publicClient.readContract({
|
|
82
|
+
address: this.addresses.registry,
|
|
83
|
+
abi: registryAbi,
|
|
84
|
+
functionName: 'getAgent',
|
|
85
|
+
args: [getAddress(address)],
|
|
86
|
+
}));
|
|
87
|
+
return {
|
|
88
|
+
address: getAddress(address),
|
|
89
|
+
owner: raw.owner,
|
|
90
|
+
pricePerTask: raw.pricePerTask,
|
|
91
|
+
currency: raw.currency,
|
|
92
|
+
active: raw.active,
|
|
93
|
+
registeredAt: raw.registeredAt,
|
|
94
|
+
metadataURI: raw.metadataURI,
|
|
95
|
+
metadata: parseAgentMetadata(raw.metadataURI),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Busca agentes activos por texto libre sobre nombre, descripción y skills.
|
|
100
|
+
*
|
|
101
|
+
* Sin `query` devuelve todos los activos. La búsqueda es del lado del cliente
|
|
102
|
+
* porque el registry no indexa texto: son pocos agentes y una lectura
|
|
103
|
+
* paginada sale más barata que montar un índice.
|
|
104
|
+
*/
|
|
105
|
+
async searchAgents(query, options = {}) {
|
|
106
|
+
const all = await this.listAgents();
|
|
107
|
+
const pool = options.includeInactive ? all : all.filter((a) => a.active);
|
|
108
|
+
if (!query?.trim())
|
|
109
|
+
return pool;
|
|
110
|
+
const needles = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
111
|
+
return pool.filter((agent) => {
|
|
112
|
+
const haystack = [agent.metadata.name, agent.metadata.description, ...agent.metadata.skills]
|
|
113
|
+
.join(' ')
|
|
114
|
+
.toLowerCase();
|
|
115
|
+
return needles.every((n) => haystack.includes(n));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** Una tarea por su id. */
|
|
119
|
+
async getTask(taskId) {
|
|
120
|
+
// El ABI declara los campos con nombre, así que viem devuelve un objeto y
|
|
121
|
+
// no una tupla: desestructurar por posición aquí compilaría pero leería
|
|
122
|
+
// basura si algún día cambia el orden.
|
|
123
|
+
const raw = (await this.publicClient.readContract({
|
|
124
|
+
address: this.addresses.escrow,
|
|
125
|
+
abi: escrowAbi,
|
|
126
|
+
functionName: 'tasks',
|
|
127
|
+
args: [taskId],
|
|
128
|
+
}));
|
|
129
|
+
return { id: taskId, ...raw };
|
|
130
|
+
}
|
|
131
|
+
/** Cuántas tareas se han creado en total (los ids van de 0 a este número - 1). */
|
|
132
|
+
async getTaskCount() {
|
|
133
|
+
return (await this.publicClient.readContract({
|
|
134
|
+
address: this.addresses.escrow,
|
|
135
|
+
abi: escrowAbi,
|
|
136
|
+
functionName: 'getTaskCount',
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
/** Saldo acreditado y pendiente de retirar, por moneda. */
|
|
140
|
+
async getPendingWithdrawal(account, currency = NATIVE_CURRENCY) {
|
|
141
|
+
return (await this.publicClient.readContract({
|
|
142
|
+
address: this.addresses.escrow,
|
|
143
|
+
abi: escrowAbi,
|
|
144
|
+
functionName: 'pendingWithdrawals',
|
|
145
|
+
args: [currency, getAddress(account)],
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
// -------------------------------------------------------------------------
|
|
149
|
+
// Escritura
|
|
150
|
+
// -------------------------------------------------------------------------
|
|
151
|
+
/**
|
|
152
|
+
* Contrata a un agente: bloquea el pago en el escrow y crea la tarea.
|
|
153
|
+
*
|
|
154
|
+
* El brief no se sube a ningún sitio; lo que va on-chain es su keccak256. Se
|
|
155
|
+
* lo tienes que hacer llegar tú al agente (por su endpoint, por el dashboard
|
|
156
|
+
* o como quieras), y el hash sirve para demostrar después qué se encargó.
|
|
157
|
+
*
|
|
158
|
+
* Si el agente cobra en $PANAL, esto hace dos transacciones: el `approve` por
|
|
159
|
+
* el importe exacto y luego `createTask`. En MON nativo va en una sola.
|
|
160
|
+
*/
|
|
161
|
+
async hire(params) {
|
|
162
|
+
const wallet = this.wallet();
|
|
163
|
+
const agent = await this.getAgent(params.agent);
|
|
164
|
+
if (!agent.active)
|
|
165
|
+
throw new Error(`El agente ${params.agent} está dado de baja: no acepta encargos.`);
|
|
166
|
+
const amount = params.amount ?? agent.pricePerTask;
|
|
167
|
+
if (amount <= 0n)
|
|
168
|
+
throw new Error('El importe tiene que ser mayor que cero.');
|
|
169
|
+
const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1000) + 24 * 60 * 60);
|
|
170
|
+
const taskHash = keccak256(toBytes(params.brief));
|
|
171
|
+
const isNative = agent.currency.toLowerCase() === NATIVE_CURRENCY.toLowerCase();
|
|
172
|
+
// Antes de nada: ¿hay saldo? Fallar aquí da un mensaje claro; fallar dentro
|
|
173
|
+
// del contrato da un revert sin contexto.
|
|
174
|
+
await this.assertFunds(agent.currency, amount, isNative);
|
|
175
|
+
if (!isNative) {
|
|
176
|
+
// approve por el importe exacto, no infinito: si el escrow tuviera un
|
|
177
|
+
// fallo, la exposición se limita a este encargo.
|
|
178
|
+
const approveHash = await wallet.writeContract({
|
|
179
|
+
address: agent.currency,
|
|
180
|
+
abi: erc20Abi,
|
|
181
|
+
functionName: 'approve',
|
|
182
|
+
args: [this.addresses.escrow, amount],
|
|
183
|
+
chain: chainFor(this.network),
|
|
184
|
+
account: this.account,
|
|
185
|
+
});
|
|
186
|
+
await this.publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
187
|
+
}
|
|
188
|
+
const txHash = await wallet.writeContract({
|
|
189
|
+
address: this.addresses.escrow,
|
|
190
|
+
abi: escrowAbi,
|
|
191
|
+
functionName: 'createTask',
|
|
192
|
+
args: [agent.address, taskHash, deadline, agent.currency, amount],
|
|
193
|
+
value: isNative ? amount : 0n,
|
|
194
|
+
chain: chainFor(this.network),
|
|
195
|
+
account: this.account,
|
|
196
|
+
});
|
|
197
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
198
|
+
if (receipt.status !== 'success')
|
|
199
|
+
throw new Error(`createTask revirtió (tx ${txHash}).`);
|
|
200
|
+
// El id es el contador ANTES de crear: se relee del recibo para no asumirlo.
|
|
201
|
+
const taskId = (await this.getTaskCount()) - 1n;
|
|
202
|
+
return { taskId, txHash, amount, currency: agent.currency, taskHash };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Aprueba el resultado y libera el pago, con una valoración de 1 a 5.
|
|
206
|
+
*
|
|
207
|
+
* Si no apruebas ni disputas, el escrow libera el pago solo a las 72 h. Este
|
|
208
|
+
* método existe para cobrar antes y, sobre todo, para que la valoración quede
|
|
209
|
+
* registrada: sin ella el agente no construye reputación.
|
|
210
|
+
*/
|
|
211
|
+
async approveTask(taskId, rating) {
|
|
212
|
+
if (!Number.isInteger(rating) || rating < 1 || rating > 5) {
|
|
213
|
+
throw new Error('La valoración es un entero de 1 a 5.');
|
|
214
|
+
}
|
|
215
|
+
const wallet = this.wallet();
|
|
216
|
+
const task = await this.getTask(taskId);
|
|
217
|
+
if (task.status !== TaskStatus.Delivered) {
|
|
218
|
+
throw new Error(`La tarea #${taskId} está "${TaskStatus[task.status]}": solo se aprueba lo entregado.`);
|
|
219
|
+
}
|
|
220
|
+
const hash = await wallet.writeContract({
|
|
221
|
+
address: this.addresses.escrow,
|
|
222
|
+
abi: escrowAbi,
|
|
223
|
+
functionName: 'approveAndRelease',
|
|
224
|
+
args: [taskId, rating],
|
|
225
|
+
chain: chainFor(this.network),
|
|
226
|
+
account: this.account,
|
|
227
|
+
});
|
|
228
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
229
|
+
return hash;
|
|
230
|
+
}
|
|
231
|
+
/** Retira lo acreditado en una moneda (patrón pull payment). */
|
|
232
|
+
async withdraw(currency = NATIVE_CURRENCY) {
|
|
233
|
+
const wallet = this.wallet();
|
|
234
|
+
const hash = await wallet.writeContract({
|
|
235
|
+
address: this.addresses.escrow,
|
|
236
|
+
abi: escrowAbi,
|
|
237
|
+
functionName: 'withdraw',
|
|
238
|
+
args: [currency],
|
|
239
|
+
chain: chainFor(this.network),
|
|
240
|
+
account: this.account,
|
|
241
|
+
});
|
|
242
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
243
|
+
return hash;
|
|
244
|
+
}
|
|
245
|
+
/** Comprueba el saldo antes de firmar, para fallar con un mensaje legible. */
|
|
246
|
+
async assertFunds(currency, amount, isNative) {
|
|
247
|
+
const owner = this.account.address;
|
|
248
|
+
const balance = isNative
|
|
249
|
+
? await this.publicClient.getBalance({ address: owner })
|
|
250
|
+
: (await this.publicClient.readContract({
|
|
251
|
+
address: currency,
|
|
252
|
+
abi: erc20Abi,
|
|
253
|
+
functionName: 'balanceOf',
|
|
254
|
+
args: [owner],
|
|
255
|
+
}));
|
|
256
|
+
if (balance < amount) {
|
|
257
|
+
const symbol = isNative ? 'MON' : '$PANAL';
|
|
258
|
+
throw new Error(`Saldo insuficiente: hacen falta ${formatEther(amount)} ${symbol} y ${owner} tiene ${formatEther(balance)}.` +
|
|
259
|
+
(isNative ? ' Además necesitas algo extra para el gas.' : ''));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/** Atajo: `createPanalClient()` sin argumentos ya habla con mainnet. */
|
|
264
|
+
export function createPanalClient(options = {}) {
|
|
265
|
+
return new PanalClient(options);
|
|
266
|
+
}
|