gn-provider 1.0.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/dist/gn-provider.d.ts +32 -0
- package/dist/gn-provider.js +254 -0
- package/package.json +33 -0
- package/scripts/install.js +36 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Provider, TransactionResponse, TxHash, UtxoQueryOptions } from 'scrypt-ts/dist/bsv/abstract-provider';
|
|
2
|
+
import * as bsv from 'bsv';
|
|
3
|
+
import { AddressOption, UTXO } from 'scrypt-ts/dist/bsv/types';
|
|
4
|
+
declare enum ProviderEvent {
|
|
5
|
+
Connected = "connected",
|
|
6
|
+
NetworkChange = "networkChange"
|
|
7
|
+
}
|
|
8
|
+
export declare class GNProvider extends Provider {
|
|
9
|
+
emit: (event: ProviderEvent, ...args: any[]) => boolean;
|
|
10
|
+
private _network;
|
|
11
|
+
private _isConnected;
|
|
12
|
+
private _apiKey;
|
|
13
|
+
constructor(network: bsv.Networks.Network, apiKey?: string);
|
|
14
|
+
get apiPrefix(): string;
|
|
15
|
+
isConnected(): boolean;
|
|
16
|
+
connect(): Promise<this>;
|
|
17
|
+
private _getHeaders;
|
|
18
|
+
updateNetwork(network: bsv.Networks.Network): void;
|
|
19
|
+
getNetwork(): bsv.Networks.Network;
|
|
20
|
+
protected _ready(): Promise<void>;
|
|
21
|
+
sendRawTransaction(rawTxHex: string): Promise<TxHash>;
|
|
22
|
+
listUnspent(address: AddressOption, options?: UtxoQueryOptions): Promise<UTXO[]>;
|
|
23
|
+
getBalance(address: AddressOption): Promise<{
|
|
24
|
+
confirmed: number;
|
|
25
|
+
unconfirmed: number;
|
|
26
|
+
}>;
|
|
27
|
+
getTransaction(txHash: string): Promise<TransactionResponse>;
|
|
28
|
+
getFeePerKb(): Promise<number>;
|
|
29
|
+
private needIgnoreError;
|
|
30
|
+
private friendlyBIP22RejectionMsg;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.GNProvider = void 0;
|
|
46
|
+
// Agrega esto al inicio del archivo
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
const events_1 = require("events");
|
|
49
|
+
const abstract_provider_1 = require("scrypt-ts/dist/bsv/abstract-provider");
|
|
50
|
+
const bsv = __importStar(require("bsv"));
|
|
51
|
+
//import superagent from 'superagent';
|
|
52
|
+
const superagent = __importStar(require("superagent"));
|
|
53
|
+
const utils_1 = require("scrypt-ts/dist/bsv/utils");
|
|
54
|
+
//import { EventEmitter } from 'events';
|
|
55
|
+
var ProviderEvent;
|
|
56
|
+
(function (ProviderEvent) {
|
|
57
|
+
ProviderEvent["Connected"] = "connected";
|
|
58
|
+
ProviderEvent["NetworkChange"] = "networkChange";
|
|
59
|
+
})(ProviderEvent || (ProviderEvent = {}));
|
|
60
|
+
class GNProvider extends abstract_provider_1.Provider {
|
|
61
|
+
constructor(network, apiKey = '') {
|
|
62
|
+
super();
|
|
63
|
+
this._isConnected = false;
|
|
64
|
+
Object.setPrototypeOf(this, events_1.EventEmitter.prototype);
|
|
65
|
+
this._network = network;
|
|
66
|
+
this._apiKey = apiKey;
|
|
67
|
+
this._initializeConnection();
|
|
68
|
+
}
|
|
69
|
+
get apiPrefix() {
|
|
70
|
+
const networkStr = this._network.name === bsv.Networks.mainnet.name ? 'main' : 'test';
|
|
71
|
+
return `https://api.whatsonchain.com/v1/bsv/${networkStr}`;
|
|
72
|
+
}
|
|
73
|
+
isConnected() {
|
|
74
|
+
return this._isConnected;
|
|
75
|
+
}
|
|
76
|
+
connect() {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
var _a;
|
|
79
|
+
try {
|
|
80
|
+
const headers = this._getHeaders();
|
|
81
|
+
const res = yield superagent.get(`${this.apiPrefix}/woc`)
|
|
82
|
+
.timeout(3000)
|
|
83
|
+
.set(headers);
|
|
84
|
+
if (res.ok && res.text === "Whats On Chain") {
|
|
85
|
+
this._isConnected = true;
|
|
86
|
+
this.emit(ProviderEvent.Connected, true);
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
throw new Error(((_a = res.body) === null || _a === void 0 ? void 0 : _a.msg) || res.text);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this._isConnected = false;
|
|
93
|
+
this.emit(ProviderEvent.Connected, false);
|
|
94
|
+
throw new Error(`connect failed: ${error.message || "unknown error"}`);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
_getHeaders() {
|
|
99
|
+
return Object.assign({ 'Content-Type': 'application/json' }, (this._apiKey ? { 'woc-api-key': this._apiKey } : {}));
|
|
100
|
+
}
|
|
101
|
+
updateNetwork(network) {
|
|
102
|
+
this._network = network;
|
|
103
|
+
this.emit(ProviderEvent.NetworkChange, network);
|
|
104
|
+
}
|
|
105
|
+
getNetwork() {
|
|
106
|
+
return this._network;
|
|
107
|
+
}
|
|
108
|
+
_ready() {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
if (!this.isConnected()) {
|
|
111
|
+
try {
|
|
112
|
+
yield this.connect();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
sendRawTransaction(rawTxHex) {
|
|
121
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
var _a;
|
|
123
|
+
yield this._ready();
|
|
124
|
+
const headers = this._getHeaders();
|
|
125
|
+
const size = Math.max(1, rawTxHex.length / 2 / 1024); // Tamaño en KB
|
|
126
|
+
const timeout = Math.max(10000, 1000 * size); // Timeout dinámico
|
|
127
|
+
try {
|
|
128
|
+
const res = yield superagent.post(`${this.apiPrefix}/tx/raw`)
|
|
129
|
+
.timeout({
|
|
130
|
+
response: timeout,
|
|
131
|
+
deadline: 60000
|
|
132
|
+
})
|
|
133
|
+
.set(headers)
|
|
134
|
+
.send({ txhex: rawTxHex });
|
|
135
|
+
return res.body;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if ((_a = error.response) === null || _a === void 0 ? void 0 : _a.text) {
|
|
139
|
+
if (this.needIgnoreError(error.response.text)) {
|
|
140
|
+
return new bsv.Transaction(rawTxHex).id;
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`GNProvider ERROR: ${this.friendlyBIP22RejectionMsg(error.response.text)}`);
|
|
143
|
+
}
|
|
144
|
+
throw new Error(`GNProvider ERROR: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
listUnspent(address, options) {
|
|
149
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
yield this._ready();
|
|
151
|
+
const headers = this._getHeaders();
|
|
152
|
+
const res = yield superagent.get(`${this.apiPrefix}/address/${address}/unspent`)
|
|
153
|
+
.set(headers);
|
|
154
|
+
const utxos = res.body.map((item) => ({
|
|
155
|
+
txId: item.tx_hash,
|
|
156
|
+
outputIndex: item.tx_pos,
|
|
157
|
+
satoshis: item.value,
|
|
158
|
+
script: bsv.Script.buildPublicKeyHashOut(address).toHex(),
|
|
159
|
+
}));
|
|
160
|
+
return options ? (0, utils_1.filterUTXO)(utxos, options) : utxos;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
getBalance(address) {
|
|
164
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
165
|
+
try {
|
|
166
|
+
const headers = this._getHeaders();
|
|
167
|
+
const res = yield superagent.get(`${this.apiPrefix}/address/${address}/balance`)
|
|
168
|
+
.set(headers);
|
|
169
|
+
return {
|
|
170
|
+
confirmed: res.body.confirmed || 0,
|
|
171
|
+
unconfirmed: res.body.unconfirmed || 0
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
// Fallback a listUnspent
|
|
176
|
+
const utxos = yield this.listUnspent(address);
|
|
177
|
+
return {
|
|
178
|
+
confirmed: utxos.reduce((acc, utxo) => acc + utxo.satoshis, 0),
|
|
179
|
+
unconfirmed: 0
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
getTransaction(txHash) {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
186
|
+
yield this._ready();
|
|
187
|
+
const headers = this._getHeaders();
|
|
188
|
+
try {
|
|
189
|
+
const res = yield superagent.get(`${this.apiPrefix}/tx/${txHash}/hex`)
|
|
190
|
+
.set(headers);
|
|
191
|
+
if (res.ok) {
|
|
192
|
+
return new bsv.Transaction(res.text);
|
|
193
|
+
}
|
|
194
|
+
throw new Error(`Transaction not found: ${txHash}`);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
throw new Error(`Error fetching transaction: ${error.message}`);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
getFeePerKb() {
|
|
202
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
203
|
+
yield this._ready();
|
|
204
|
+
const headers = this._getHeaders();
|
|
205
|
+
try {
|
|
206
|
+
const now = Math.floor(Date.now() / 1000);
|
|
207
|
+
const from = now - 1800; // 30 minutos atrás
|
|
208
|
+
const res = yield superagent.get(`${this.apiPrefix}/miner/fees?from=${from}&to=${now}`)
|
|
209
|
+
.set(headers);
|
|
210
|
+
if (res.body && Array.isArray(res.body) && res.body.length > 0) {
|
|
211
|
+
const totalFeeRate = res.body.reduce((sum, minerData) => {
|
|
212
|
+
return sum + minerData.min_fee_rate;
|
|
213
|
+
}, 0);
|
|
214
|
+
const averageFeeRate = totalFeeRate / res.body.length;
|
|
215
|
+
const feeRateWithMargin = averageFeeRate * 1.3;
|
|
216
|
+
return Math.round(feeRateWithMargin * 100) / 100;
|
|
217
|
+
}
|
|
218
|
+
throw new Error("No fee data available");
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return 1.05; // Valor de fallback
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
needIgnoreError(inMsg) {
|
|
226
|
+
if (inMsg.includes('Transaction already in the mempool'))
|
|
227
|
+
return true;
|
|
228
|
+
if (inMsg.includes('txn-already-known'))
|
|
229
|
+
return true;
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
friendlyBIP22RejectionMsg(inMsg) {
|
|
233
|
+
const messages = {
|
|
234
|
+
'bad-txns-vin-empty': 'Transaction is missing inputs.',
|
|
235
|
+
'bad-txns-vout-empty': 'Transaction is missing outputs.',
|
|
236
|
+
'bad-txns-oversize': 'Transaction is too large.',
|
|
237
|
+
'bad-txns-vout-negative': 'Transaction output value is negative.',
|
|
238
|
+
'bad-txns-vout-toolarge': 'Transaction output value is too large.',
|
|
239
|
+
'bad-txns-txouttotal-toolarge': 'Transaction total output value is too large.',
|
|
240
|
+
'bad-txns-prevout-null': 'Transaction inputs previous TX reference is null.',
|
|
241
|
+
'bad-txns-inputs-duplicate': 'Transaction contains duplicate inputs.',
|
|
242
|
+
'bad-txns-inputs-too-large': 'Transaction inputs too large.',
|
|
243
|
+
'bad-txns-fee-negative': 'Transaction network fee is negative.',
|
|
244
|
+
'bad-txns-fee-outofrange': 'Transaction network fee is out of range.',
|
|
245
|
+
'mandatory-script-verify-flag-failed': 'Script evaluation failed.'
|
|
246
|
+
};
|
|
247
|
+
for (const [key, msg] of Object.entries(messages)) {
|
|
248
|
+
if (inMsg.includes(key))
|
|
249
|
+
return msg;
|
|
250
|
+
}
|
|
251
|
+
return inMsg;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
exports.GNProvider = GNProvider;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gn-provider",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist",
|
|
6
|
+
"scripts"
|
|
7
|
+
],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node scripts/install.js"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"scrypt-ts": "^1.4.5"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"bsv",
|
|
16
|
+
"scrypt",
|
|
17
|
+
"provider",
|
|
18
|
+
"bitcoin",
|
|
19
|
+
"whatsonchain"],
|
|
20
|
+
"main": "dist/gn-provider.js",
|
|
21
|
+
"types": "dist/gn-provider.d.ts",
|
|
22
|
+
"author": "BJ",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"description": "This is a BSV blockchain provider for sCrpyt using Whatsonchain API",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"fs-extra": "^11.3.0",
|
|
27
|
+
"typescript": "^5.8.3"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^24.0.3",
|
|
31
|
+
"@types/superagent": "^8.1.9"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
async function install() {
|
|
5
|
+
try {
|
|
6
|
+
// 1. Encontrar la ubicación de scrypt-ts
|
|
7
|
+
const scryptPath = path.dirname(require.resolve('scrypt-ts'));
|
|
8
|
+
const targetDir = path.join(scryptPath, 'dist', 'providers');
|
|
9
|
+
|
|
10
|
+
// 2. Verificar que existe
|
|
11
|
+
if (!fs.existsSync(targetDir)) {
|
|
12
|
+
throw new Error(`scrypt-ts providers directory not found at: ${targetDir}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 3. Copiar archivos
|
|
16
|
+
const sourceFiles = [
|
|
17
|
+
path.join(__dirname, '../dist/gn-provider.js'),
|
|
18
|
+
path.join(__dirname, '../dist/gn-provider.d.ts')
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
await Promise.all(sourceFiles.map(file => {
|
|
22
|
+
const filename = path.basename(file);
|
|
23
|
+
const target = path.join(targetDir, filename);
|
|
24
|
+
return fs.copy(file, target);
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
console.log('✅ GN Provider instalado exitosamente en scrypt-ts');
|
|
28
|
+
console.log(`📍 Ubicación: ${targetDir}`);
|
|
29
|
+
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('❌ Error instalando GN Provider:', error.message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
install();
|