gn-provider 1.2.6 → 1.2.8

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
@@ -122,7 +122,7 @@ Creates a new provider instance.
122
122
  | `listUnspent(address: string): Promise<UTXO[]>` | Lists UTXOs for an address |
123
123
  | `getBalance(address: string): Promise<{confirmed: number, unconfirmed: number}>` | Gets address balance |
124
124
  | `getTransaction(txHash: string): Promise<Transaction>` | Retrieves transaction details |
125
- | `getFeePerKb(): Promise<number>` | Estimates current fee rate |
125
+ | `getFeePerKb(): Promise<number>` | Enhanced estimation of the fee rate based on current block stats |
126
126
 
127
127
  ## Features
128
128
 
@@ -5,6 +5,9 @@ declare enum ProviderEvent {
5
5
  Connected = "connected",
6
6
  NetworkChange = "networkChange"
7
7
  }
8
+ export interface GNProviderOptions {
9
+ bridgeUrl?: string;
10
+ }
8
11
  export declare class GNProvider extends Provider {
9
12
  emit: (event: ProviderEvent, ...args: any[]) => boolean;
10
13
  private _network;
@@ -13,10 +16,11 @@ export declare class GNProvider extends Provider {
13
16
  private _gorillaPoolApiKey;
14
17
  private apiPrefix;
15
18
  private _mapiURL;
19
+ private _bridgeUrl?;
16
20
  private _getHeaders;
17
21
  connect: () => Promise<this>;
18
22
  getFeePerKb: () => Promise<number>;
19
- constructor(network: scryptlib.bsv.Networks.Network, wocApiKey?: string, gpApiKey?: string);
23
+ constructor(network: scryptlib.bsv.Networks.Network, wocApiKey?: string, gpApiKey?: string, options?: GNProviderOptions);
20
24
  isConnected: () => boolean;
21
25
  updateNetwork: (network: scryptlib.bsv.Networks.Network) => void;
22
26
  getNetwork: () => scryptlib.bsv.Networks.Network;
@@ -53,11 +53,8 @@ var ProviderEvent;
53
53
  ProviderEvent["Connected"] = "connected";
54
54
  ProviderEvent["NetworkChange"] = "networkChange";
55
55
  })(ProviderEvent || (ProviderEvent = {}));
56
- /*export type UTXOWithHeight = UTXO & {
57
- height: number;
58
- };*/
59
56
  class GNProvider extends abstract_provider_1.Provider {
60
- constructor(network, wocApiKey = '', gpApiKey = '') {
57
+ constructor(network, wocApiKey = '', gpApiKey = '', options) {
61
58
  super();
62
59
  this._isConnected = false;
63
60
  this._getHeaders = () => {
@@ -87,32 +84,28 @@ class GNProvider extends abstract_provider_1.Provider {
87
84
  yield this._ready();
88
85
  const headers = this._getHeaders();
89
86
  try {
90
- // Paso 1: Obtener la información de la cadena para obtener la altura actual
91
- const chainInfoRes = yield superagent.get(`${this.apiPrefix()}/chain/info`)
92
- .set(headers);
87
+ const chainInfoRes = yield superagent.get(`${this.apiPrefix()}/chain/info`).set(headers);
93
88
  const currentHeight = chainInfoRes.body.blocks;
94
- // Paso 2: Obtener las estadísticas del bloque actual
95
- const blockStatsRes = yield superagent.get(`${this.apiPrefix()}/block/height/${currentHeight}/stats`)
96
- .set(headers);
97
- const blockStats = blockStatsRes.body;
98
- const totalFee = blockStats.total_fee;
99
- const size = blockStats.size;
100
- // Si el tamaño es 0, evitar división por cero
101
- if (size === 0) {
102
- throw new Error('Block size is zero');
103
- }
104
- // Calcular tarifa por kilobyte: (total_fee * 1024) / size
105
- const feePerKb = (totalFee * 1024) / size;
106
- // Aplicar un multiplicador para asegurar una tarifa competitiva (1.5x)
89
+ const blockStatsRes = yield superagent.get(`${this.apiPrefix()}/block/height/${currentHeight}/stats`).set(headers);
90
+ const stats = blockStatsRes.body;
91
+ // 1. Extraer mediana de fee y tamaño medio de transacción
92
+ const medianFeePerTx = stats.median_fee; // Satoshis por transacción
93
+ const medianSizePerTx = stats.median_tx_size; // Bytes por transacción
94
+ if (!medianSizePerTx || medianSizePerTx === 0)
95
+ throw new Error('Invalid block stats');
96
+ // 2. Calcular la tasa: (Satoshis / Bytes) * 1000 para obtener sat/kb
97
+ const feePerKb = (medianFeePerTx / medianSizePerTx) * 1000;
98
+ // 3. Aplicar multiplicador competitivo (1.5x es seguro en BSV)
107
99
  const competitiveFee = feePerKb * 1.5;
108
- // Establecer un mínimo de 50 sat/kb y un máximo de 5000 sat/kb para evitar valores extremos
109
- const safeFee = Math.max(50, Math.min(competitiveFee, 5000));
110
- console.log(`Calculated fee rate: ${safeFee.toFixed(2)} sat/kb from block ${currentHeight}`);
100
+ // 4. SafeFee con tus nuevos límites
101
+ // Nota: Subimos el mínimo a 1.1 para asegurar prioridad sobre el polvo de la red
102
+ const safeFee = Math.max(1.1, Math.min(competitiveFee, 500));
103
+ console.log(`Calculated MEDIAN fee rate: ${safeFee.toFixed(2)} sat/kb`);
111
104
  return Math.round(safeFee * 100) / 100;
112
105
  }
113
106
  catch (error) {
114
- console.warn('Fee estimation from block stats failed, using fallback');
115
- return 500; // Fallback a 500 sat/kb
107
+ console.warn('Fee estimation failed, using fallback');
108
+ return 1.1; // Fallback mucho más realista para BSV que 500
116
109
  }
117
110
  });
118
111
  this.isConnected = () => this._isConnected;
@@ -225,24 +218,25 @@ class GNProvider extends abstract_provider_1.Provider {
225
218
  throw new Error(`GNProvider ERROR: ${error.message}`);
226
219
  }
227
220
  }*/
228
- this.sendRawTransaction = (rawTxHex) => __awaiter(this, void 0, void 0, function* () {
229
- var _a;
230
- yield this._ready();
221
+ /*EN USO
222
+ sendRawTransaction = async (rawTxHex: string): Promise<TxHash> => {
223
+ await this._ready();
231
224
  const headers = this._getHeaders();
232
225
  const size = Math.max(1, rawTxHex.length / 2 / 1024);
233
226
  const timeout = Math.max(10000, 1000 * size);
227
+
234
228
  try {
235
- const res = yield superagent.post(`${this.apiPrefix()}/tx/raw`)
229
+ const res = await superagent.post(`${this.apiPrefix()}/tx/raw`)
236
230
  .timeout({
237
- response: timeout,
238
- deadline: 60000
239
- })
231
+ response: timeout,
232
+ deadline: 60000
233
+ })
240
234
  .set(headers)
241
235
  .send({ txhex: rawTxHex });
236
+
242
237
  return res.body;
243
- }
244
- catch (error) {
245
- if ((_a = error.response) === null || _a === void 0 ? void 0 : _a.text) {
238
+ } catch (error: any) {
239
+ if (error.response?.text) {
246
240
  if (this.needIgnoreError(error.response.text)) {
247
241
  return new scryptlib.bsv.Transaction(rawTxHex).id;
248
242
  }
@@ -250,74 +244,48 @@ class GNProvider extends abstract_provider_1.Provider {
250
244
  }
251
245
  throw new Error(`GNProvider ERROR: ${error.message}`);
252
246
  }
253
- });
254
- /*sendRawTransaction = async (rawTxHex: string): Promise<TxHash> => {
255
- await this._ready();
256
- const headers = this._getHeaders();
257
- const size = Math.max(1, rawTxHex.length / 2 / 1024);
258
- const timeout = Math.max(15000, 1000 * size);
259
-
260
- const wocRequest = superagent.post(`${this.apiPrefix()}/tx/raw`)
261
- .timeout({ response: timeout, deadline: 60000 })
262
- .set(headers)
263
- .send({ txhex: rawTxHex });
264
-
265
- const gpRequest = superagent.post(this._mapiURL + 'tx')
266
- .timeout({ response: timeout, deadline: 60000 })
267
- .set('Content-Type', 'application/octet-stream')
268
- .send(Buffer.from(rawTxHex, 'hex'));
269
-
270
- if (this._gorillaPoolApiKey) {
271
- gpRequest.set('Authorization', `Bearer ${this._gorillaPoolApiKey}`);
272
- }
273
-
247
+ }*/
248
+ this.sendRawTransaction = (rawTxHex) => __awaiter(this, void 0, void 0, function* () {
249
+ var _a, _b;
250
+ yield this._ready();
251
+ const isUsingBridge = !!this._bridgeUrl;
252
+ const url = isUsingBridge
253
+ ? `${this._bridgeUrl}/broadcast`
254
+ : `${this.apiPrefix()}/tx/raw`;
274
255
  try {
275
- const responses = await Promise.allSettled([
276
- wocRequest.then(res => ({ source: 'WhatsOnChain', result: res.body })),
277
- gpRequest.then(res => {
278
- try {
279
- if (!res.body?.payload) {
280
- throw new Error('Respuesta inválida de GorillaPool: falta payload');
281
- }
282
-
283
- const payload = JSON.parse(res.body.payload);
284
-
285
- if (payload.returnResult === 'success') {
286
- return { source: 'GorillaPool', result: payload.txid };
287
- } else {
288
- throw new Error(`GorillaPool [${payload.returnResult || 'unknown'}]: ${payload.resultDescription || 'Sin descripción'}`);
289
- }
290
- } catch (parseError) {
291
- const message = parseError instanceof Error ? parseError.message : String(parseError);
292
- throw new Error(`GorillaPool: Error parsing response - ${message}`);
293
- }
294
- })
295
- ]);
296
-
297
- for (const response of responses) {
298
- if (response.status === 'fulfilled') {
299
- console.log(`✅ Transacción aceptada por: ${response.value.source}`);
300
- return response.value.result;
301
- }
302
- }
303
-
304
- const firstRejection = responses.find(r => r.status === 'rejected');
305
- if (firstRejection) {
306
- throw new Error(firstRejection.reason.message || firstRejection.reason);
256
+ console.log(`🚀 [GNProvider] Broadcasting via ${isUsingBridge ? 'GoldenNotes Bridge' : 'WhatsOnChain'}...`);
257
+ const res = yield superagent
258
+ .post(url)
259
+ .send({
260
+ txhex: rawTxHex,
261
+ network: this._network.name === scryptlib.bsv.Networks.mainnet.name ? 'main' : 'test'
262
+ })
263
+ .timeout(35000); // 35s para dar margen al polling del bridge
264
+ // Tu bridge devuelve el TXID como texto (res.text)
265
+ //const txid = typeof res.body === 'string' ? res.body : (res.text || res.body?.txid);
266
+ //const txid = res.text || (res.body && res.body.txid) || res.body;
267
+ const txid = isUsingBridge
268
+ ? (res.text || (res.body && res.body.txid) || res.body)
269
+ : res.body;
270
+ if (typeof txid !== 'string' || txid.length !== 64) {
271
+ return new scryptlib.bsv.Transaction(rawTxHex).id;
307
272
  }
308
-
309
- throw new Error('Todos los intentos de envío fallaron');
310
-
311
- } catch (error: any) {
312
- if (error.response?.text) {
313
- if (this.needIgnoreError(error.response.text)) {
314
- return new scryptlib.bsv.Transaction(rawTxHex).id;
315
- }
316
- throw new Error(`GNProvider ERROR: ${this.friendlyBIP22RejectionMsg(error.response.text)}`);
273
+ console.log(`✅ [GNProvider] Éxito: ${txid}`);
274
+ return txid;
275
+ }
276
+ catch (error) {
277
+ const errorMsg = ((_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) || error.message || "";
278
+ // --- LOGICA DE TOLERANCIA ---
279
+ // Si el bridge falla porque la TX ya existe, no detenemos el contrato.
280
+ if (this.needIgnoreError(errorMsg)) {
281
+ const recoveredTxid = new scryptlib.bsv.Transaction(rawTxHex).id;
282
+ console.log(`ℹ️ [GNProvider] La TX ya estaba en red. ID recuperado: ${recoveredTxid}`);
283
+ return recoveredTxid;
317
284
  }
318
- throw new Error(`GNProvider ERROR: ${error.message}`);
285
+ console.error(`❌ [GNProvider] Error crítico: ${errorMsg}`);
286
+ throw new Error(`GNProvider ERROR: ${this.friendlyBIP22RejectionMsg(errorMsg)}`);
319
287
  }
320
- }*/
288
+ });
321
289
  this.sendTransaction = (signedTx) => __awaiter(this, void 0, void 0, function* () {
322
290
  try {
323
291
  const txHex = signedTx.serialize({ disableIsFullySigned: true });
@@ -438,6 +406,7 @@ class GNProvider extends abstract_provider_1.Provider {
438
406
  const networkStr = this._network.name === scryptlib.bsv.Networks.mainnet.name ? 'main' : 'test';
439
407
  return `https://api.whatsonchain.com/v1/bsv/${networkStr}`;
440
408
  };
409
+ this._bridgeUrl = options === null || options === void 0 ? void 0 : options.bridgeUrl;
441
410
  Object.setPrototypeOf(this, events_1.EventEmitter.prototype);
442
411
  this.connect().catch(console.error);
443
412
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gn-provider",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "files": [
5
5
  "dist",
6
6
  "scripts",