@stacks/node-publisher-client 0.2.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 +299 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +203 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/drop-mempool-tx.d.ts +7 -0
- package/dist/messages/drop-mempool-tx.js +3 -0
- package/dist/messages/drop-mempool-tx.js.map +1 -0
- package/dist/messages/index.d.ts +48 -0
- package/dist/messages/index.js +37 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/new-block.d.ts +357 -0
- package/dist/messages/new-block.js +18 -0
- package/dist/messages/new-block.js.map +1 -0
- package/dist/messages/new-burn-block.d.ts +20 -0
- package/dist/messages/new-burn-block.js +3 -0
- package/dist/messages/new-burn-block.js.map +1 -0
- package/dist/messages/new-mempool-tx.d.ts +2 -0
- package/dist/messages/new-mempool-tx.js +3 -0
- package/dist/messages/new-mempool-tx.js.map +1 -0
- package/dist/messages/stackerdb-chunks.d.ts +17 -0
- package/dist/messages/stackerdb-chunks.js +3 -0
- package/dist/messages/stackerdb-chunks.js.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# @stacks/node-publisher-client
|
|
2
|
+
|
|
3
|
+
A TypeScript client library for consuming Stacks blockchain events from the [Stacks Node Publisher](https://github.com/stx-labs/stacks-node-publisher) service.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @stacks/node-publisher-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import {
|
|
15
|
+
StacksMessageStream,
|
|
16
|
+
MessagePath,
|
|
17
|
+
StreamPosition,
|
|
18
|
+
Message,
|
|
19
|
+
} from '@stacks/node-publisher-client';
|
|
20
|
+
|
|
21
|
+
// Create the stream client
|
|
22
|
+
const stream = new StacksMessageStream({
|
|
23
|
+
appName: 'my-app',
|
|
24
|
+
redisUrl: 'redis://localhost:6379',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Connect to Redis
|
|
28
|
+
await stream.connect({ waitForReady: true });
|
|
29
|
+
|
|
30
|
+
// Define where to start streaming from
|
|
31
|
+
const getStartPosition = async (): Promise<StreamPosition> => {
|
|
32
|
+
return null; // Start from the beginning
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Handle incoming messages
|
|
36
|
+
const handleMessage = async (id: string, timestamp: string, message: Message) => {
|
|
37
|
+
console.log(`Received ${message.path} at ${timestamp}`);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Start streaming
|
|
41
|
+
stream.start(getStartPosition, handleMessage);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Reference
|
|
45
|
+
|
|
46
|
+
### `StacksMessageStream`
|
|
47
|
+
|
|
48
|
+
The main client class for connecting to and consuming events from a Stacks Node Publisher service.
|
|
49
|
+
|
|
50
|
+
#### Constructor Options
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
new StacksMessageStream({
|
|
54
|
+
appName: string; // Required: Unique identifier for your application
|
|
55
|
+
redisUrl?: string; // Redis connection URL (default: localhost)
|
|
56
|
+
redisStreamPrefix?: string; // Prefix for Redis stream keys
|
|
57
|
+
options?: {
|
|
58
|
+
selectedMessagePaths?: MessagePath[] | '*'; // Filter by message types (default: '*')
|
|
59
|
+
batchSize?: number; // Messages per batch (default: 100)
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Methods
|
|
65
|
+
|
|
66
|
+
##### `connect(options: { waitForReady: boolean }): Promise<void>`
|
|
67
|
+
|
|
68
|
+
Connects to the Redis server.
|
|
69
|
+
|
|
70
|
+
- `waitForReady: true` - Blocks until connected (recommended for startup)
|
|
71
|
+
- `waitForReady: false` - Connects in the background
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
await stream.connect({ waitForReady: true });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
##### `start(positionCallback, messageCallback): void`
|
|
78
|
+
|
|
79
|
+
Starts consuming the event stream.
|
|
80
|
+
|
|
81
|
+
- `positionCallback: () => Promise<StreamPosition>` - Called to determine where to start/resume streaming
|
|
82
|
+
- `messageCallback: (id, timestamp, message) => Promise<void>` - Called for each received message
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
stream.start(
|
|
86
|
+
async () => ({ indexBlockHash: '0x...', blockHeight: 150000 }),
|
|
87
|
+
async (id, timestamp, message) => {
|
|
88
|
+
// Process message
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
##### `stop(): Promise<void>`
|
|
94
|
+
|
|
95
|
+
Gracefully stops the stream and closes the Redis connection.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
await stream.stop();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `StreamPosition`
|
|
102
|
+
|
|
103
|
+
Defines where to start or resume the event stream.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
type StreamPosition =
|
|
107
|
+
| { indexBlockHash: string; blockHeight: number } // Start from a specific block
|
|
108
|
+
| { messageId: string } // Start from a specific message ID
|
|
109
|
+
| null; // Start from the beginning
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Message Types
|
|
113
|
+
|
|
114
|
+
### `MessagePath`
|
|
115
|
+
|
|
116
|
+
Enum of all available message paths:
|
|
117
|
+
|
|
118
|
+
| Path | Description |
|
|
119
|
+
|------|-------------|
|
|
120
|
+
| `MessagePath.NewBlock` | New Stacks block with transactions and events |
|
|
121
|
+
| `MessagePath.NewBurnBlock` | Bitcoin anchor block information |
|
|
122
|
+
| `MessagePath.NewMempoolTx` | Transactions entering the mempool |
|
|
123
|
+
| `MessagePath.DropMempoolTx` | Transactions removed from the mempool |
|
|
124
|
+
| `MessagePath.StackerDbChunks` | Signer and StackerDB data chunks |
|
|
125
|
+
| `MessagePath.NewMicroblocks` | Microblock data (legacy) |
|
|
126
|
+
| `MessagePath.ProposalResponse` | Miner block proposal responses |
|
|
127
|
+
| `MessagePath.AttachmentsNew` | Attachment data (legacy) |
|
|
128
|
+
|
|
129
|
+
### `NewBlockMessage`
|
|
130
|
+
|
|
131
|
+
Contains full block data including:
|
|
132
|
+
|
|
133
|
+
- Block metadata (hash, height, timestamps)
|
|
134
|
+
- All transactions with execution results
|
|
135
|
+
- Events (STX transfers, contract events, NFT/FT operations)
|
|
136
|
+
- Miner rewards
|
|
137
|
+
- Signer information (epoch 3+)
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
interface NewBlockMessage {
|
|
141
|
+
block_hash: string;
|
|
142
|
+
block_height: number;
|
|
143
|
+
index_block_hash: string;
|
|
144
|
+
burn_block_hash: string;
|
|
145
|
+
burn_block_height: number;
|
|
146
|
+
burn_block_time: number;
|
|
147
|
+
transactions: NewBlockTransaction[];
|
|
148
|
+
events: NewBlockEvent[];
|
|
149
|
+
matured_miner_rewards: MinerReward[];
|
|
150
|
+
// ... additional fields
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `NewBurnBlockMessage`
|
|
155
|
+
|
|
156
|
+
Bitcoin block anchoring information:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface NewBurnBlockMessage {
|
|
160
|
+
burn_block_hash: string;
|
|
161
|
+
burn_block_height: number;
|
|
162
|
+
burn_amount: number; // BTC satoshis
|
|
163
|
+
reward_recipients: { recipient: string; amt: number }[];
|
|
164
|
+
reward_slot_holders: string[]; // Bitcoin addresses
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `NewMempoolTxMessage`
|
|
169
|
+
|
|
170
|
+
Array of raw hex-encoded transactions entering the mempool:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
type NewMempoolTxMessage = string[];
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### `DropMempoolTxMessage`
|
|
177
|
+
|
|
178
|
+
Transactions removed from the mempool:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
interface DropMempoolTxMessage {
|
|
182
|
+
dropped_txids: string[];
|
|
183
|
+
reason: 'ReplaceByFee' | 'ReplaceAcrossFork' | 'TooExpensive' | 'StaleGarbageCollect' | 'Problematic';
|
|
184
|
+
new_txid: string | null;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### `StackerDbChunksMessage`
|
|
189
|
+
|
|
190
|
+
Signer and StackerDB data chunks:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
interface StackerDbChunksMessage {
|
|
194
|
+
contract_id: { issuer: [number, number[]]; name: string };
|
|
195
|
+
modified_slots: {
|
|
196
|
+
slot_id: number;
|
|
197
|
+
slot_version: number;
|
|
198
|
+
data: string; // hex string
|
|
199
|
+
sig: string; // hex signature
|
|
200
|
+
}[];
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Usage Examples
|
|
205
|
+
|
|
206
|
+
### Filtering by Message Type
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const stream = new StacksMessageStream({
|
|
210
|
+
appName: 'block-indexer',
|
|
211
|
+
redisUrl: 'redis://localhost:6379',
|
|
212
|
+
options: {
|
|
213
|
+
selectedMessagePaths: [MessagePath.NewBlock, MessagePath.NewBurnBlock],
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Resuming from Last Processed Block
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const getStartPosition = async (): Promise<StreamPosition> => {
|
|
222
|
+
const lastBlock = await db.getLastProcessedBlock();
|
|
223
|
+
if (lastBlock) {
|
|
224
|
+
return {
|
|
225
|
+
indexBlockHash: lastBlock.indexBlockHash,
|
|
226
|
+
blockHeight: lastBlock.height,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return null; // Start from beginning if no prior state
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
stream.start(getStartPosition, handleMessage);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Processing Block Events
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { MessagePath, NewBlockMessage, NewBlockEventType } from '@stacks/node-publisher-client';
|
|
239
|
+
|
|
240
|
+
const handleMessage = async (id: string, timestamp: string, message: Message) => {
|
|
241
|
+
if (message.path !== MessagePath.NewBlock) return;
|
|
242
|
+
|
|
243
|
+
const block: NewBlockMessage = message.payload;
|
|
244
|
+
|
|
245
|
+
for (const event of block.events) {
|
|
246
|
+
switch (event.type) {
|
|
247
|
+
case NewBlockEventType.StxTransfer:
|
|
248
|
+
console.log(`STX Transfer: ${event.stx_transfer_event.amount} microSTX`);
|
|
249
|
+
console.log(` From: ${event.stx_transfer_event.sender}`);
|
|
250
|
+
console.log(` To: ${event.stx_transfer_event.recipient}`);
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case NewBlockEventType.Contract:
|
|
254
|
+
console.log(`Contract Event: ${event.contract_event.contract_identifier}`);
|
|
255
|
+
console.log(` Topic: ${event.contract_event.topic}`);
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
case NewBlockEventType.NftMint:
|
|
259
|
+
console.log(`NFT Minted: ${event.nft_mint_event.asset_identifier}`);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Handling Reconnection
|
|
267
|
+
|
|
268
|
+
The client automatically handles reconnection. You can listen for the `redisConsumerGroupDestroyed` event to perform additional cleanup:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
stream.events.on('redisConsumerGroupDestroyed', () => {
|
|
272
|
+
console.log('Consumer group was destroyed, will automatically reconnect');
|
|
273
|
+
// Optionally refresh your position callback state
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## TypeScript Support
|
|
278
|
+
|
|
279
|
+
This package is written in TypeScript and includes full type definitions. All message types are fully typed:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import type {
|
|
283
|
+
Message,
|
|
284
|
+
MessagePath,
|
|
285
|
+
StreamPosition,
|
|
286
|
+
NewBlockMessage,
|
|
287
|
+
NewBlockEvent,
|
|
288
|
+
NewBlockTransaction,
|
|
289
|
+
NewBurnBlockMessage,
|
|
290
|
+
NewMempoolTxMessage,
|
|
291
|
+
DropMempoolTxMessage,
|
|
292
|
+
StackerDbChunksMessage,
|
|
293
|
+
ClarityAbi,
|
|
294
|
+
} from '@stacks/node-publisher-client';
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## License
|
|
298
|
+
|
|
299
|
+
GPL-3.0-only
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { RedisClientType } from 'redis';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { Message, MessagePath } from './messages';
|
|
4
|
+
/**
|
|
5
|
+
* The starting position for the message stream. Can be either an index block hash with block height
|
|
6
|
+
* or a message ID.
|
|
7
|
+
* - `indexBlockHash` + `blockHeight`: The index block hash and height of the Stacks block to start
|
|
8
|
+
* from. The backend will resolve this to the corresponding message ID. If the block hash doesn't
|
|
9
|
+
* exist but the height is higher than the highest available, it will start from the highest
|
|
10
|
+
* available block.
|
|
11
|
+
* - `messageId`: The message ID to start from. The backend will validate this ID exists and is not
|
|
12
|
+
* greater than the highest available ID.
|
|
13
|
+
*
|
|
14
|
+
* If neither is provided or validation fails, the stream will start from the beginning.
|
|
15
|
+
*/
|
|
16
|
+
export type StreamPosition = {
|
|
17
|
+
indexBlockHash: string;
|
|
18
|
+
blockHeight: number;
|
|
19
|
+
} | {
|
|
20
|
+
messageId: string;
|
|
21
|
+
} | null;
|
|
22
|
+
/**
|
|
23
|
+
* The callback function for retrieving the starting position for the event stream. Should return
|
|
24
|
+
* either an index block hash with block height or a message ID. This callback is used to determine
|
|
25
|
+
* the starting message ID for the event stream and may be called periodically on reconnection.
|
|
26
|
+
*/
|
|
27
|
+
export type StreamPositionCallback = () => Promise<StreamPosition>;
|
|
28
|
+
/**
|
|
29
|
+
* The callback function for event stream ingestion. Will be called for each message in the event
|
|
30
|
+
* stream. The callback should return a promise that resolves when the message has been processed.
|
|
31
|
+
*/
|
|
32
|
+
export type MessageCallback = (
|
|
33
|
+
/** The message ID. */
|
|
34
|
+
id: string,
|
|
35
|
+
/** The timestamp of the message. */
|
|
36
|
+
timestamp: string,
|
|
37
|
+
/** The message */
|
|
38
|
+
message: Message) => Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Message paths to include in a message stream, where `*` means client wants to receive all
|
|
41
|
+
* messages.
|
|
42
|
+
*/
|
|
43
|
+
export type SelectedMessagePaths = MessagePath[] | '*';
|
|
44
|
+
/**
|
|
45
|
+
* Stacks message stream options.
|
|
46
|
+
*/
|
|
47
|
+
export type StreamOptions = {
|
|
48
|
+
/**
|
|
49
|
+
* Message paths to include in a message stream, where `*` means client wants to receive all
|
|
50
|
+
* messages.
|
|
51
|
+
*/
|
|
52
|
+
selectedMessagePaths?: SelectedMessagePaths;
|
|
53
|
+
/**
|
|
54
|
+
* The batch size for the message stream.
|
|
55
|
+
*/
|
|
56
|
+
batchSize?: number;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* A client for a Stacks core node message stream.
|
|
60
|
+
*/
|
|
61
|
+
export declare class StacksMessageStream {
|
|
62
|
+
static readonly GROUP_NAME = "primary_group";
|
|
63
|
+
static readonly CONSUMER_NAME = "primary_consumer";
|
|
64
|
+
readonly client: RedisClientType;
|
|
65
|
+
clientId: `${string}-${string}-${string}-${string}-${string}`;
|
|
66
|
+
private readonly selectedPaths;
|
|
67
|
+
private readonly redisStreamPrefix;
|
|
68
|
+
private readonly appName;
|
|
69
|
+
private readonly abort;
|
|
70
|
+
private readonly streamWaiter;
|
|
71
|
+
private readonly logger;
|
|
72
|
+
private readonly msgBatchSize;
|
|
73
|
+
/** For testing purposes only. The last message ID that was processed by this client. */
|
|
74
|
+
lastProcessedMessageId: string;
|
|
75
|
+
/** For testing purposes only. The connection status of the client. */
|
|
76
|
+
connectionStatus: 'not_started' | 'connected' | 'reconnecting' | 'ended';
|
|
77
|
+
readonly events: EventEmitter<{
|
|
78
|
+
redisConsumerGroupDestroyed: [];
|
|
79
|
+
msgReceived: [{
|
|
80
|
+
id: string;
|
|
81
|
+
}];
|
|
82
|
+
}>;
|
|
83
|
+
constructor(args: {
|
|
84
|
+
appName: string;
|
|
85
|
+
redisUrl?: string;
|
|
86
|
+
redisStreamPrefix?: string;
|
|
87
|
+
options?: StreamOptions;
|
|
88
|
+
});
|
|
89
|
+
sanitizeRedisClientName(name: string): string;
|
|
90
|
+
get redisClientName(): string;
|
|
91
|
+
connect({ waitForReady }: {
|
|
92
|
+
waitForReady: boolean;
|
|
93
|
+
}): Promise<void>;
|
|
94
|
+
start(positionCallback: StreamPositionCallback, messageCallback: MessageCallback): void;
|
|
95
|
+
private ingestEventStream;
|
|
96
|
+
stop(): Promise<void>;
|
|
97
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StacksMessageStream = void 0;
|
|
4
|
+
const redis_1 = require("redis");
|
|
5
|
+
const api_toolkit_1 = require("@hirosystems/api-toolkit");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const node_events_1 = require("node:events");
|
|
8
|
+
/**
|
|
9
|
+
* A client for a Stacks core node message stream.
|
|
10
|
+
*/
|
|
11
|
+
class StacksMessageStream {
|
|
12
|
+
constructor(args) {
|
|
13
|
+
this.clientId = (0, node_crypto_1.randomUUID)();
|
|
14
|
+
this.logger = api_toolkit_1.logger.child({ module: 'StacksMessageStream' });
|
|
15
|
+
/** For testing purposes only. The last message ID that was processed by this client. */
|
|
16
|
+
this.lastProcessedMessageId = '0-0';
|
|
17
|
+
/** For testing purposes only. The connection status of the client. */
|
|
18
|
+
this.connectionStatus = 'not_started';
|
|
19
|
+
this.events = new node_events_1.EventEmitter();
|
|
20
|
+
this.selectedPaths = args.options?.selectedMessagePaths ?? '*';
|
|
21
|
+
this.abort = new AbortController();
|
|
22
|
+
this.streamWaiter = (0, api_toolkit_1.waiter)();
|
|
23
|
+
this.redisStreamPrefix = args.redisStreamPrefix ?? '';
|
|
24
|
+
if (this.redisStreamPrefix !== '' && !this.redisStreamPrefix.endsWith(':')) {
|
|
25
|
+
this.redisStreamPrefix += ':';
|
|
26
|
+
}
|
|
27
|
+
this.appName = this.sanitizeRedisClientName(args.appName);
|
|
28
|
+
this.msgBatchSize = args.options?.batchSize ?? 100;
|
|
29
|
+
this.client = (0, redis_1.createClient)({
|
|
30
|
+
url: args.redisUrl,
|
|
31
|
+
name: this.redisClientName,
|
|
32
|
+
disableOfflineQueue: true,
|
|
33
|
+
});
|
|
34
|
+
// Must have a listener for 'error' events to avoid unhandled exceptions
|
|
35
|
+
this.client.on('error', (err) => this.logger.error(err, `Redis error for client ${this.clientId}`));
|
|
36
|
+
this.client.on('reconnecting', () => {
|
|
37
|
+
this.connectionStatus = 'reconnecting';
|
|
38
|
+
this.logger.info(`Reconnecting to Redis for client ${this.clientId}`);
|
|
39
|
+
});
|
|
40
|
+
this.client.on('ready', () => {
|
|
41
|
+
this.connectionStatus = 'connected';
|
|
42
|
+
this.logger.info(`Redis connection ready for client ${this.clientId}`);
|
|
43
|
+
});
|
|
44
|
+
this.client.on('end', () => {
|
|
45
|
+
this.connectionStatus = 'ended';
|
|
46
|
+
this.logger.info(`Redis connection ended for client ${this.clientId}`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Sanitize the redis client name to only include valid characters (same approach used in the
|
|
50
|
+
// StackExchange.RedisClient https://github.com/StackExchange/StackExchange.Redis/pull/2654/files)
|
|
51
|
+
sanitizeRedisClientName(name) {
|
|
52
|
+
const nameSanitizer = /[^!-~]+/g;
|
|
53
|
+
return name.trim().replace(nameSanitizer, '-');
|
|
54
|
+
}
|
|
55
|
+
get redisClientName() {
|
|
56
|
+
return `${this.redisStreamPrefix}snp-consumer:${this.appName}:${this.clientId}`;
|
|
57
|
+
}
|
|
58
|
+
async connect({ waitForReady }) {
|
|
59
|
+
// Taken from `RedisBroker`.
|
|
60
|
+
if (waitForReady) {
|
|
61
|
+
while (true) {
|
|
62
|
+
try {
|
|
63
|
+
await this.client.connect();
|
|
64
|
+
this.logger.info(`Connected to Redis for client ${this.clientId}`);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
this.logger.error(err, `Error connecting to Redis for client ${this.clientId}, retrying...`);
|
|
69
|
+
await (0, api_toolkit_1.timeout)(500);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
void this.client.connect().catch((err) => {
|
|
75
|
+
this.logger.error(err, `Error connecting to Redis for client ${this.clientId}, retrying...`);
|
|
76
|
+
void (0, api_toolkit_1.timeout)(500).then(() => this.connect({ waitForReady }));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
start(positionCallback, messageCallback) {
|
|
81
|
+
this.logger.info(`Starting stream ingestion for client ${this.clientId}`);
|
|
82
|
+
const runIngest = async () => {
|
|
83
|
+
while (!this.abort.signal.aborted) {
|
|
84
|
+
try {
|
|
85
|
+
const startingPosition = await positionCallback();
|
|
86
|
+
this.logger.info(`Starting position: ${JSON.stringify(startingPosition)}`);
|
|
87
|
+
await this.ingestEventStream(startingPosition, messageCallback);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (this.abort.signal.aborted) {
|
|
91
|
+
this.logger.info('Stream ingestion aborted');
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
else if (error.message?.includes('NOGROUP')) {
|
|
95
|
+
// The redis stream doesn't exist. This can happen if the redis server was restarted,
|
|
96
|
+
// or if the client is idle/offline, or if the client is processing messages too slowly.
|
|
97
|
+
// If this code path is reached, then we're obviously online so we just need to re-initialize
|
|
98
|
+
// the connection.
|
|
99
|
+
this.logger.error(error, `The Redis stream group for this client was destroyed by the server for client ${this.clientId}`);
|
|
100
|
+
this.events.emit('redisConsumerGroupDestroyed');
|
|
101
|
+
// re-announce connection, re-create group, etc
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// TODO: what are other expected errors and how should we handle them? For now we just retry
|
|
106
|
+
// forever.
|
|
107
|
+
this.logger.error(error, `Error reading or acknowledging from stream for client ${this.clientId}`);
|
|
108
|
+
this.logger.info('Reconnecting to Redis stream in 1 second...');
|
|
109
|
+
await (0, api_toolkit_1.timeout)(1000);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
void runIngest()
|
|
116
|
+
.then(() => {
|
|
117
|
+
this.streamWaiter.finish();
|
|
118
|
+
})
|
|
119
|
+
.catch((err) => {
|
|
120
|
+
this.logger.error(err, 'Ingestion stream error');
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async ingestEventStream(startingPosition, eventCallback) {
|
|
124
|
+
// Reset clientId for each new connection, this prevents race-conditions around cleanup
|
|
125
|
+
// for any previous connections.
|
|
126
|
+
this.clientId = (0, node_crypto_1.randomUUID)();
|
|
127
|
+
this.logger.info(`Connecting to Redis stream with clientId: ${this.clientId}`);
|
|
128
|
+
const streamKey = `${this.redisStreamPrefix}client:${this.clientId}`;
|
|
129
|
+
await this.client.clientSetName(this.redisClientName);
|
|
130
|
+
const handshakeMsg = {
|
|
131
|
+
client_id: this.clientId,
|
|
132
|
+
last_index_block_hash: startingPosition && 'indexBlockHash' in startingPosition
|
|
133
|
+
? startingPosition.indexBlockHash
|
|
134
|
+
: '',
|
|
135
|
+
last_block_height: startingPosition && 'blockHeight' in startingPosition
|
|
136
|
+
? startingPosition.blockHeight.toString()
|
|
137
|
+
: '',
|
|
138
|
+
last_message_id: startingPosition && 'messageId' in startingPosition ? startingPosition.messageId : '',
|
|
139
|
+
app_name: this.appName,
|
|
140
|
+
selected_paths: JSON.stringify(this.selectedPaths),
|
|
141
|
+
};
|
|
142
|
+
await this.client
|
|
143
|
+
.multi()
|
|
144
|
+
// Announce connection
|
|
145
|
+
.xAdd(this.redisStreamPrefix + 'connection_stream', '*', handshakeMsg)
|
|
146
|
+
// Create group for this stream
|
|
147
|
+
.xGroupCreate(streamKey, StacksMessageStream.GROUP_NAME, '$', {
|
|
148
|
+
MKSTREAM: true,
|
|
149
|
+
})
|
|
150
|
+
.exec();
|
|
151
|
+
// Start reading messages from the stream.
|
|
152
|
+
while (!this.abort.signal.aborted) {
|
|
153
|
+
// The backend creates the group with the correct starting position, so we use '>' here to
|
|
154
|
+
// get only messages after the last message ID.
|
|
155
|
+
const results = await this.client.xReadGroup(StacksMessageStream.GROUP_NAME, StacksMessageStream.CONSUMER_NAME, {
|
|
156
|
+
key: streamKey,
|
|
157
|
+
id: '>',
|
|
158
|
+
}, {
|
|
159
|
+
COUNT: this.msgBatchSize,
|
|
160
|
+
BLOCK: 1000, // Wait 1 second for new events.
|
|
161
|
+
});
|
|
162
|
+
if (!results) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
for (const stream of results) {
|
|
166
|
+
if (stream.messages.length > 0) {
|
|
167
|
+
this.logger.debug(`Received messages ${stream.messages[0].id} - ${stream.messages[stream.messages.length - 1].id} with client ${this.clientId}`);
|
|
168
|
+
}
|
|
169
|
+
for (const item of stream.messages) {
|
|
170
|
+
await eventCallback(item.id, item.message.timestamp, {
|
|
171
|
+
path: item.message.path,
|
|
172
|
+
payload: JSON.parse(item.message.body),
|
|
173
|
+
});
|
|
174
|
+
this.lastProcessedMessageId = item.id;
|
|
175
|
+
this.events.emit('msgReceived', { id: item.id });
|
|
176
|
+
await this.client
|
|
177
|
+
.multi()
|
|
178
|
+
// Acknowledge the message so that it is removed from the server's Pending Entries List (PEL)
|
|
179
|
+
.xAck(streamKey, StacksMessageStream.GROUP_NAME, item.id)
|
|
180
|
+
// Delete the message from the stream so that it doesn't get reprocessed
|
|
181
|
+
.xDel(streamKey, item.id)
|
|
182
|
+
.exec();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async stop() {
|
|
188
|
+
this.abort.abort();
|
|
189
|
+
await this.streamWaiter;
|
|
190
|
+
await this.client.close().catch((error) => {
|
|
191
|
+
if (error.message?.includes('client is closed')) {
|
|
192
|
+
// ignore
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.StacksMessageStream = StacksMessageStream;
|
|
201
|
+
StacksMessageStream.GROUP_NAME = 'primary_group';
|
|
202
|
+
StacksMessageStream.CONSUMER_NAME = 'primary_consumer';
|
|
203
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type DropMempoolTxReasonType = 'ReplaceByFee' | 'ReplaceAcrossFork' | 'TooExpensive' | 'StaleGarbageCollect' | 'Problematic';
|
|
2
|
+
/** Message sent when a transaction is dropped from the mempool. */
|
|
3
|
+
export interface DropMempoolTxMessage {
|
|
4
|
+
dropped_txids: string[];
|
|
5
|
+
reason: DropMempoolTxReasonType;
|
|
6
|
+
new_txid: string | null;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drop-mempool-tx.js","sourceRoot":"","sources":["../../src/messages/drop-mempool-tx.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DropMempoolTxMessage } from './drop-mempool-tx';
|
|
2
|
+
import { NewBlockMessage } from './new-block';
|
|
3
|
+
import { NewBurnBlockMessage } from './new-burn-block';
|
|
4
|
+
import { NewMempoolTxMessage } from './new-mempool-tx';
|
|
5
|
+
import { StackerDbChunksMessage } from './stackerdb-chunks';
|
|
6
|
+
export * from './drop-mempool-tx';
|
|
7
|
+
export * from './new-block';
|
|
8
|
+
export * from './new-burn-block';
|
|
9
|
+
export * from './new-mempool-tx';
|
|
10
|
+
export * from './stackerdb-chunks';
|
|
11
|
+
/**
|
|
12
|
+
* The path of the Stacks message as sent by the Stacks node.
|
|
13
|
+
*/
|
|
14
|
+
export declare enum MessagePath {
|
|
15
|
+
NewBlock = "/new_block",
|
|
16
|
+
NewBurnBlock = "/new_burn_block",
|
|
17
|
+
NewMempoolTx = "/new_mempool_tx",
|
|
18
|
+
DropMempoolTx = "/drop_mempool_tx",
|
|
19
|
+
NewMicroblocks = "/new_microblocks",
|
|
20
|
+
StackerDbChunks = "/stackerdb_chunks",
|
|
21
|
+
ProposalResponse = "/proposal_response",
|
|
22
|
+
AttachmentsNew = "/attachments/new"
|
|
23
|
+
}
|
|
24
|
+
export type Message = {
|
|
25
|
+
path: MessagePath.NewBlock;
|
|
26
|
+
payload: NewBlockMessage;
|
|
27
|
+
} | {
|
|
28
|
+
path: MessagePath.NewBurnBlock;
|
|
29
|
+
payload: NewBurnBlockMessage;
|
|
30
|
+
} | {
|
|
31
|
+
path: MessagePath.NewMempoolTx;
|
|
32
|
+
payload: NewMempoolTxMessage;
|
|
33
|
+
} | {
|
|
34
|
+
path: MessagePath.DropMempoolTx;
|
|
35
|
+
payload: DropMempoolTxMessage;
|
|
36
|
+
} | {
|
|
37
|
+
path: MessagePath.StackerDbChunks;
|
|
38
|
+
payload: StackerDbChunksMessage;
|
|
39
|
+
} | {
|
|
40
|
+
path: MessagePath.NewMicroblocks;
|
|
41
|
+
payload: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
path: MessagePath.ProposalResponse;
|
|
44
|
+
payload: unknown;
|
|
45
|
+
} | {
|
|
46
|
+
path: MessagePath.AttachmentsNew;
|
|
47
|
+
payload: unknown;
|
|
48
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.MessagePath = void 0;
|
|
18
|
+
__exportStar(require("./drop-mempool-tx"), exports);
|
|
19
|
+
__exportStar(require("./new-block"), exports);
|
|
20
|
+
__exportStar(require("./new-burn-block"), exports);
|
|
21
|
+
__exportStar(require("./new-mempool-tx"), exports);
|
|
22
|
+
__exportStar(require("./stackerdb-chunks"), exports);
|
|
23
|
+
/**
|
|
24
|
+
* The path of the Stacks message as sent by the Stacks node.
|
|
25
|
+
*/
|
|
26
|
+
var MessagePath;
|
|
27
|
+
(function (MessagePath) {
|
|
28
|
+
MessagePath["NewBlock"] = "/new_block";
|
|
29
|
+
MessagePath["NewBurnBlock"] = "/new_burn_block";
|
|
30
|
+
MessagePath["NewMempoolTx"] = "/new_mempool_tx";
|
|
31
|
+
MessagePath["DropMempoolTx"] = "/drop_mempool_tx";
|
|
32
|
+
MessagePath["NewMicroblocks"] = "/new_microblocks";
|
|
33
|
+
MessagePath["StackerDbChunks"] = "/stackerdb_chunks";
|
|
34
|
+
MessagePath["ProposalResponse"] = "/proposal_response";
|
|
35
|
+
MessagePath["AttachmentsNew"] = "/attachments/new";
|
|
36
|
+
})(MessagePath || (exports.MessagePath = MessagePath = {}));
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAMA,oDAAkC;AAClC,8CAA4B;AAC5B,mDAAiC;AACjC,mDAAiC;AACjC,qDAAmC;AAEnC;;GAEG;AACH,IAAY,WASX;AATD,WAAY,WAAW;IACrB,sCAAuB,CAAA;IACvB,+CAAgC,CAAA;IAChC,+CAAgC,CAAA;IAChC,iDAAkC,CAAA;IAClC,kDAAmC,CAAA;IACnC,oDAAqC,CAAA;IACrC,sDAAuC,CAAA;IACvC,kDAAmC,CAAA;AACrC,CAAC,EATW,WAAW,2BAAX,WAAW,QAStB"}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
export declare enum NewBlockEventType {
|
|
2
|
+
Contract = "contract_event",
|
|
3
|
+
StxTransfer = "stx_transfer_event",
|
|
4
|
+
StxMint = "stx_mint_event",
|
|
5
|
+
StxBurn = "stx_burn_event",
|
|
6
|
+
StxLock = "stx_lock_event",
|
|
7
|
+
NftTransfer = "nft_transfer_event",
|
|
8
|
+
NftMint = "nft_mint_event",
|
|
9
|
+
NftBurn = "nft_burn_event",
|
|
10
|
+
FtTransfer = "ft_transfer_event",
|
|
11
|
+
FtMint = "ft_mint_event",
|
|
12
|
+
FtBurn = "ft_burn_event"
|
|
13
|
+
}
|
|
14
|
+
interface NewBlockEventBase {
|
|
15
|
+
/** 0x-prefix transaction hash. */
|
|
16
|
+
txid: string;
|
|
17
|
+
event_index: number;
|
|
18
|
+
committed: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface NewBlockContractEvent extends NewBlockEventBase {
|
|
21
|
+
type: NewBlockEventType.Contract;
|
|
22
|
+
contract_event: {
|
|
23
|
+
/** Fully qualified contract ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.kv-store" */
|
|
24
|
+
contract_identifier: string;
|
|
25
|
+
topic: string;
|
|
26
|
+
/** @deprecated Use `raw_value` instead. */
|
|
27
|
+
value: unknown;
|
|
28
|
+
/** Hex encoded Clarity value. */
|
|
29
|
+
raw_value: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface NewBlockStxTransferEvent extends NewBlockEventBase {
|
|
33
|
+
type: NewBlockEventType.StxTransfer;
|
|
34
|
+
stx_transfer_event: {
|
|
35
|
+
recipient: string;
|
|
36
|
+
sender: string;
|
|
37
|
+
amount: string;
|
|
38
|
+
/** Hex-encoded string. Only provided when a memo was specified in the Clarity `stx-transfer?` function (requires a Stacks 2.1 contract). */
|
|
39
|
+
memo?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface NewBlockStxMintEvent extends NewBlockEventBase {
|
|
43
|
+
type: NewBlockEventType.StxMint;
|
|
44
|
+
stx_mint_event: {
|
|
45
|
+
recipient: string;
|
|
46
|
+
amount: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
interface NewBlockStxBurnEvent extends NewBlockEventBase {
|
|
50
|
+
type: NewBlockEventType.StxBurn;
|
|
51
|
+
stx_burn_event: {
|
|
52
|
+
sender: string;
|
|
53
|
+
amount: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface NewBlockStxLockEvent extends NewBlockEventBase {
|
|
57
|
+
type: NewBlockEventType.StxLock;
|
|
58
|
+
committed: boolean;
|
|
59
|
+
stx_lock_event: {
|
|
60
|
+
/** String quoted base10 integer. */
|
|
61
|
+
locked_amount: string;
|
|
62
|
+
/** String quoted base10 integer. */
|
|
63
|
+
unlock_height: string;
|
|
64
|
+
/** STX principal associated with the locked tokens. */
|
|
65
|
+
locked_address: string;
|
|
66
|
+
/** Fully qualified contract ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.pox" or "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.pox-2" */
|
|
67
|
+
contract_identifier?: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
interface NewBlockNftTransferEvent extends NewBlockEventBase {
|
|
71
|
+
type: NewBlockEventType.NftTransfer;
|
|
72
|
+
nft_transfer_event: {
|
|
73
|
+
/** Fully qualified asset ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.contract-name.asset-name" */
|
|
74
|
+
asset_identifier: string;
|
|
75
|
+
recipient: string;
|
|
76
|
+
sender: string;
|
|
77
|
+
/** @deprecated Use `raw_value` instead. */
|
|
78
|
+
value: unknown;
|
|
79
|
+
/** Hex encoded Clarity value. */
|
|
80
|
+
raw_value: string;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export interface NewBlockNftMintEvent extends NewBlockEventBase {
|
|
84
|
+
type: NewBlockEventType.NftMint;
|
|
85
|
+
nft_mint_event: {
|
|
86
|
+
/** Fully qualified asset ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.contract-name.asset-name" */
|
|
87
|
+
asset_identifier: string;
|
|
88
|
+
recipient: string;
|
|
89
|
+
/** @deprecated Use `raw_value` instead. */
|
|
90
|
+
value: unknown;
|
|
91
|
+
/** Hex encoded Clarity value. */
|
|
92
|
+
raw_value: string;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
interface NewBlockNftBurnEvent extends NewBlockEventBase {
|
|
96
|
+
type: NewBlockEventType.NftBurn;
|
|
97
|
+
nft_burn_event: {
|
|
98
|
+
/** Fully qualified asset ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.contract-name.asset-name" */
|
|
99
|
+
asset_identifier: string;
|
|
100
|
+
sender: string;
|
|
101
|
+
/** @deprecated Use `raw_value` instead. */
|
|
102
|
+
value: unknown;
|
|
103
|
+
/** Hex encoded Clarity value. */
|
|
104
|
+
raw_value: string;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
interface NewBlockFtTransferEvent extends NewBlockEventBase {
|
|
108
|
+
type: NewBlockEventType.FtTransfer;
|
|
109
|
+
ft_transfer_event: {
|
|
110
|
+
/** Fully qualified asset ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.contract-name.asset-name" */
|
|
111
|
+
asset_identifier: string;
|
|
112
|
+
recipient: string;
|
|
113
|
+
sender: string;
|
|
114
|
+
amount: string;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export interface NewBlockFtMintEvent extends NewBlockEventBase {
|
|
118
|
+
type: NewBlockEventType.FtMint;
|
|
119
|
+
ft_mint_event: {
|
|
120
|
+
/** Fully qualified asset ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.contract-name.asset-name" */
|
|
121
|
+
asset_identifier: string;
|
|
122
|
+
recipient: string;
|
|
123
|
+
amount: string;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
interface NewBlockFtBurnEvent extends NewBlockEventBase {
|
|
127
|
+
type: NewBlockEventType.FtBurn;
|
|
128
|
+
ft_burn_event: {
|
|
129
|
+
/** Fully qualified asset ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.contract-name.asset-name" */
|
|
130
|
+
asset_identifier: string;
|
|
131
|
+
sender: string;
|
|
132
|
+
amount: string;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export interface BurnchainOpRegisterAssetNft {
|
|
136
|
+
register_asset: {
|
|
137
|
+
asset_type: 'nft';
|
|
138
|
+
burn_header_hash: string;
|
|
139
|
+
l1_contract_id: string;
|
|
140
|
+
l2_contract_id: string;
|
|
141
|
+
txid: string;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export interface BurnchainOpRegisterAssetFt {
|
|
145
|
+
register_asset: {
|
|
146
|
+
asset_type: 'ft';
|
|
147
|
+
burn_header_hash: string;
|
|
148
|
+
l1_contract_id: string;
|
|
149
|
+
l2_contract_id: string;
|
|
150
|
+
txid: string;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export interface BurnchainOpStackStx {
|
|
154
|
+
stack_stx: {
|
|
155
|
+
auth_id: number;
|
|
156
|
+
burn_block_height: number;
|
|
157
|
+
burn_header_hash: string;
|
|
158
|
+
burn_txid: string;
|
|
159
|
+
max_amount: number;
|
|
160
|
+
num_cycles: number;
|
|
161
|
+
reward_addr: string;
|
|
162
|
+
sender: {
|
|
163
|
+
address: string;
|
|
164
|
+
address_hash_bytes: string;
|
|
165
|
+
address_version: number;
|
|
166
|
+
};
|
|
167
|
+
signer_key: string;
|
|
168
|
+
stacked_ustx: number;
|
|
169
|
+
vtxindex: number;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export interface BurnchainOpDelegateStx {
|
|
173
|
+
delegate_stx: {
|
|
174
|
+
burn_block_height: number;
|
|
175
|
+
burn_header_hash: string;
|
|
176
|
+
burn_txid: string;
|
|
177
|
+
delegate_to: {
|
|
178
|
+
address: string;
|
|
179
|
+
address_hash_bytes: string;
|
|
180
|
+
address_version: number;
|
|
181
|
+
};
|
|
182
|
+
delegated_ustx: number;
|
|
183
|
+
reward_addr: [
|
|
184
|
+
number,
|
|
185
|
+
string
|
|
186
|
+
];
|
|
187
|
+
sender: {
|
|
188
|
+
address: string;
|
|
189
|
+
address_hash_bytes: string;
|
|
190
|
+
address_version: number;
|
|
191
|
+
};
|
|
192
|
+
until_burn_height: number;
|
|
193
|
+
vtxindex: number;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
type BurnchainOp = BurnchainOpRegisterAssetNft | BurnchainOpRegisterAssetFt | BurnchainOpStackStx | BurnchainOpDelegateStx;
|
|
197
|
+
export type NewBlockEvent = NewBlockContractEvent | NewBlockStxTransferEvent | NewBlockStxMintEvent | NewBlockStxBurnEvent | NewBlockStxLockEvent | NewBlockFtTransferEvent | NewBlockFtMintEvent | NewBlockFtBurnEvent | NewBlockNftTransferEvent | NewBlockNftMintEvent | NewBlockNftBurnEvent;
|
|
198
|
+
export type NewBlockTransactionStatus = 'success' | 'abort_by_response' | 'abort_by_post_condition';
|
|
199
|
+
type ClarityAbiTypeBuffer = {
|
|
200
|
+
buffer: {
|
|
201
|
+
length: number;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
type ClarityAbiTypeResponse = {
|
|
205
|
+
response: {
|
|
206
|
+
ok: ClarityAbiType;
|
|
207
|
+
error: ClarityAbiType;
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
type ClarityAbiTypeOptional = {
|
|
211
|
+
optional: ClarityAbiType;
|
|
212
|
+
};
|
|
213
|
+
type ClarityAbiTypeTuple = {
|
|
214
|
+
tuple: {
|
|
215
|
+
name: string;
|
|
216
|
+
type: ClarityAbiType;
|
|
217
|
+
}[];
|
|
218
|
+
};
|
|
219
|
+
type ClarityAbiTypeList = {
|
|
220
|
+
list: {
|
|
221
|
+
type: ClarityAbiType;
|
|
222
|
+
length: number;
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
type ClarityAbiTypeUInt128 = 'uint128';
|
|
226
|
+
type ClarityAbiTypeInt128 = 'int128';
|
|
227
|
+
type ClarityAbiTypeBool = 'bool';
|
|
228
|
+
type ClarityAbiTypePrincipal = 'principal';
|
|
229
|
+
type ClarityAbiTypeNone = 'none';
|
|
230
|
+
type ClarityAbiTypePrimitive = ClarityAbiTypeUInt128 | ClarityAbiTypeInt128 | ClarityAbiTypeBool | ClarityAbiTypePrincipal | ClarityAbiTypeNone;
|
|
231
|
+
type ClarityAbiType = ClarityAbiTypePrimitive | ClarityAbiTypeBuffer | ClarityAbiTypeResponse | ClarityAbiTypeOptional | ClarityAbiTypeTuple | ClarityAbiTypeList;
|
|
232
|
+
interface ClarityAbiFunction {
|
|
233
|
+
name: string;
|
|
234
|
+
access: 'private' | 'public' | 'read_only';
|
|
235
|
+
args: {
|
|
236
|
+
name: string;
|
|
237
|
+
type: ClarityAbiType;
|
|
238
|
+
}[];
|
|
239
|
+
outputs: {
|
|
240
|
+
type: ClarityAbiType;
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
interface ClarityAbiVariable {
|
|
244
|
+
name: string;
|
|
245
|
+
access: 'variable' | 'constant';
|
|
246
|
+
type: ClarityAbiType;
|
|
247
|
+
}
|
|
248
|
+
interface ClarityAbiMap {
|
|
249
|
+
name: string;
|
|
250
|
+
key: {
|
|
251
|
+
name: string;
|
|
252
|
+
type: ClarityAbiType;
|
|
253
|
+
}[];
|
|
254
|
+
value: {
|
|
255
|
+
name: string;
|
|
256
|
+
type: ClarityAbiType;
|
|
257
|
+
}[];
|
|
258
|
+
}
|
|
259
|
+
interface ClarityAbiTypeFungibleToken {
|
|
260
|
+
name: string;
|
|
261
|
+
}
|
|
262
|
+
interface ClarityAbiTypeNonFungibleToken {
|
|
263
|
+
name: string;
|
|
264
|
+
type: ClarityAbiType;
|
|
265
|
+
}
|
|
266
|
+
export interface ClarityAbi {
|
|
267
|
+
functions: ClarityAbiFunction[];
|
|
268
|
+
variables: ClarityAbiVariable[];
|
|
269
|
+
maps: ClarityAbiMap[];
|
|
270
|
+
fungible_tokens: ClarityAbiTypeFungibleToken[];
|
|
271
|
+
non_fungible_tokens: ClarityAbiTypeNonFungibleToken[];
|
|
272
|
+
}
|
|
273
|
+
interface NewBlockExecutionCost {
|
|
274
|
+
read_count: number;
|
|
275
|
+
read_length: number;
|
|
276
|
+
runtime: number;
|
|
277
|
+
write_count: number;
|
|
278
|
+
write_length: number;
|
|
279
|
+
}
|
|
280
|
+
export interface NewBlockTransaction {
|
|
281
|
+
raw_tx: string;
|
|
282
|
+
status: NewBlockTransactionStatus;
|
|
283
|
+
raw_result: string;
|
|
284
|
+
txid: string;
|
|
285
|
+
tx_index: number;
|
|
286
|
+
contract_interface: ClarityAbi | null;
|
|
287
|
+
/** @deprecated Use `contract_interface` instead. The node renamed `contract_abi` to `contract_interface`. */
|
|
288
|
+
contract_abi?: ClarityAbi | null;
|
|
289
|
+
execution_cost: NewBlockExecutionCost;
|
|
290
|
+
microblock_sequence: number | null;
|
|
291
|
+
microblock_hash: string | null;
|
|
292
|
+
microblock_parent_hash: string | null;
|
|
293
|
+
vm_error?: string | null;
|
|
294
|
+
burnchain_op?: BurnchainOp | null;
|
|
295
|
+
}
|
|
296
|
+
export interface NewBlockMessage {
|
|
297
|
+
block_hash: string;
|
|
298
|
+
block_height: number;
|
|
299
|
+
burn_block_time: number;
|
|
300
|
+
burn_block_hash: string;
|
|
301
|
+
burn_block_height: number;
|
|
302
|
+
miner_txid: string;
|
|
303
|
+
index_block_hash: string;
|
|
304
|
+
parent_index_block_hash: string;
|
|
305
|
+
parent_block_hash: string;
|
|
306
|
+
parent_microblock: string;
|
|
307
|
+
parent_microblock_sequence: number;
|
|
308
|
+
parent_burn_block_hash: string;
|
|
309
|
+
parent_burn_block_height: number;
|
|
310
|
+
parent_burn_block_timestamp: number;
|
|
311
|
+
events: NewBlockEvent[];
|
|
312
|
+
transactions: NewBlockTransaction[];
|
|
313
|
+
matured_miner_rewards: {
|
|
314
|
+
from_index_consensus_hash: string;
|
|
315
|
+
from_stacks_block_hash: string;
|
|
316
|
+
/** STX principal */
|
|
317
|
+
recipient: string;
|
|
318
|
+
/** STX principal (available starting in Stacks 2.1) */
|
|
319
|
+
miner_address: string | null;
|
|
320
|
+
/** String quoted micro-STX amount. */
|
|
321
|
+
coinbase_amount: string;
|
|
322
|
+
/** String quoted micro-STX amount. */
|
|
323
|
+
tx_fees_anchored: string;
|
|
324
|
+
/** String quoted micro-STX amount. */
|
|
325
|
+
tx_fees_streamed_confirmed: string;
|
|
326
|
+
/** String quoted micro-STX amount. */
|
|
327
|
+
tx_fees_streamed_produced: string;
|
|
328
|
+
}[];
|
|
329
|
+
anchored_cost?: NewBlockExecutionCost;
|
|
330
|
+
confirmed_microblocks_cost?: NewBlockExecutionCost;
|
|
331
|
+
pox_v1_unlock_height?: number;
|
|
332
|
+
pox_v2_unlock_height?: number;
|
|
333
|
+
pox_v3_unlock_height?: number;
|
|
334
|
+
/** Available starting in epoch3, only included in blocks where the pox cycle rewards are first calculated */
|
|
335
|
+
cycle_number?: number;
|
|
336
|
+
/** AKA `coinbase_height`. In epoch2.x this is the same as `block_height`. In epoch3 this is used to track tenure heights. Only available starting in stacks-core 3.0.0.0.0-rc6 */
|
|
337
|
+
tenure_height?: number | null;
|
|
338
|
+
/** Available starting in epoch3, only included in blocks where the pox cycle rewards are first calculated */
|
|
339
|
+
reward_set?: {
|
|
340
|
+
pox_ustx_threshold: string;
|
|
341
|
+
rewarded_addresses: string[];
|
|
342
|
+
signers?: {
|
|
343
|
+
signing_key: string;
|
|
344
|
+
weight: number;
|
|
345
|
+
stacked_amt: string;
|
|
346
|
+
}[];
|
|
347
|
+
start_cycle_state: {
|
|
348
|
+
missed_reward_slots: [];
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
block_time: number | null;
|
|
352
|
+
signer_bitvec?: string | null;
|
|
353
|
+
signer_signature?: string[];
|
|
354
|
+
signer_signature_hash: string;
|
|
355
|
+
miner_signature: string;
|
|
356
|
+
}
|
|
357
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NewBlockEventType = void 0;
|
|
4
|
+
var NewBlockEventType;
|
|
5
|
+
(function (NewBlockEventType) {
|
|
6
|
+
NewBlockEventType["Contract"] = "contract_event";
|
|
7
|
+
NewBlockEventType["StxTransfer"] = "stx_transfer_event";
|
|
8
|
+
NewBlockEventType["StxMint"] = "stx_mint_event";
|
|
9
|
+
NewBlockEventType["StxBurn"] = "stx_burn_event";
|
|
10
|
+
NewBlockEventType["StxLock"] = "stx_lock_event";
|
|
11
|
+
NewBlockEventType["NftTransfer"] = "nft_transfer_event";
|
|
12
|
+
NewBlockEventType["NftMint"] = "nft_mint_event";
|
|
13
|
+
NewBlockEventType["NftBurn"] = "nft_burn_event";
|
|
14
|
+
NewBlockEventType["FtTransfer"] = "ft_transfer_event";
|
|
15
|
+
NewBlockEventType["FtMint"] = "ft_mint_event";
|
|
16
|
+
NewBlockEventType["FtBurn"] = "ft_burn_event";
|
|
17
|
+
})(NewBlockEventType || (exports.NewBlockEventType = NewBlockEventType = {}));
|
|
18
|
+
//# sourceMappingURL=new-block.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new-block.js","sourceRoot":"","sources":["../../src/messages/new-block.ts"],"names":[],"mappings":";;;AAAA,IAAY,iBAYX;AAZD,WAAY,iBAAiB;IAC3B,gDAA2B,CAAA;IAC3B,uDAAkC,CAAA;IAClC,+CAA0B,CAAA;IAC1B,+CAA0B,CAAA;IAC1B,+CAA0B,CAAA;IAC1B,uDAAkC,CAAA;IAClC,+CAA0B,CAAA;IAC1B,+CAA0B,CAAA;IAC1B,qDAAgC,CAAA;IAChC,6CAAwB,CAAA;IACxB,6CAAwB,CAAA;AAC1B,CAAC,EAZW,iBAAiB,iCAAjB,iBAAiB,QAY5B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface NewBurnBlockMessage {
|
|
2
|
+
burn_block_hash: string;
|
|
3
|
+
burn_block_height: number;
|
|
4
|
+
/** Amount in BTC satoshis. */
|
|
5
|
+
burn_amount: number;
|
|
6
|
+
reward_recipients: [
|
|
7
|
+
{
|
|
8
|
+
/** Bitcoin address (b58 encoded). */
|
|
9
|
+
recipient: string;
|
|
10
|
+
/** Amount in BTC satoshis. */
|
|
11
|
+
amt: number;
|
|
12
|
+
}
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Array of the Bitcoin addresses that would validly receive PoX commitments during this block.
|
|
16
|
+
* These addresses may not actually receive rewards during this block if the block is faster
|
|
17
|
+
* than miners have an opportunity to commit.
|
|
18
|
+
*/
|
|
19
|
+
reward_slot_holders: string[];
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new-burn-block.js","sourceRoot":"","sources":["../../src/messages/new-burn-block.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new-mempool-tx.js","sourceRoot":"","sources":["../../src/messages/new-mempool-tx.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface StackerDbChunksModifiedSlot {
|
|
2
|
+
/** Slot identifier (unique for each DB instance) */
|
|
3
|
+
slot_id: number;
|
|
4
|
+
/** Slot version (a lamport clock) */
|
|
5
|
+
slot_version: number;
|
|
6
|
+
/** Chunk data (use the sha512_256 hashed of this for generating a signature) */
|
|
7
|
+
data: string;
|
|
8
|
+
/** signature over the above */
|
|
9
|
+
sig: string;
|
|
10
|
+
}
|
|
11
|
+
export interface StackerDbChunksMessage {
|
|
12
|
+
contract_id: {
|
|
13
|
+
issuer: [number, number[]];
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
modified_slots: StackerDbChunksModifiedSlot[];
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stackerdb-chunks.js","sourceRoot":"","sources":["../../src/messages/stackerdb-chunks.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stacks/node-publisher-client",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A client to consume Stacks events from the Stacks Node Publisher service",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"typings": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "rimraf ./dist && tsc --project tsconfig.build.json",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/stx-labs/stacks-node-publisher.git",
|
|
17
|
+
"directory": "client"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"stacks",
|
|
21
|
+
"node-publisher",
|
|
22
|
+
"client"
|
|
23
|
+
],
|
|
24
|
+
"author": "Stacks Labs",
|
|
25
|
+
"license": "GPL-3.0-only",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"rimraf": "^6.0.1",
|
|
28
|
+
"typescript": "^5.7.2"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@hirosystems/api-toolkit": "^1.12.0",
|
|
32
|
+
"redis": "^5.10.0"
|
|
33
|
+
}
|
|
34
|
+
}
|