@odatano/nightgate 0.1.0 → 0.1.1

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.
@@ -1,4 +1,4 @@
1
- using { midnight } from '../db/schema';
1
+ using {midnight} from '../db/schema';
2
2
 
3
3
  /**
4
4
  * Nightgate Blockchain OData V4 API Service
@@ -6,7 +6,7 @@ using { midnight } from '../db/schema';
6
6
  * This service exposes Midnight blockchain data as a Nightgate OData V4 API,
7
7
  * following enterprise architecture patterns.
8
8
  */
9
- @path: '/api/v1/nightgate'
9
+ @path : '/api/v1/nightgate'
10
10
  @requires: 'authenticated-user'
11
11
  service NightgateService {
12
12
 
@@ -14,193 +14,215 @@ service NightgateService {
14
14
  // Blockchain Core - Read-Only Access
15
15
  // ========================================================================
16
16
 
17
- /**
18
- * Blocks endpoint with full navigation capabilities
19
- * Supports: $filter, $orderby, $expand, $select, $top, $skip
20
- */
17
+ /**
18
+ * Blocks endpoint with full navigation capabilities
19
+ * Supports: $filter, $orderby, $expand, $select, $top, $skip
20
+ */
21
21
  @readonly
22
- entity Blocks as projection on midnight.Blocks {
23
- *,
24
- parent,
25
- transactions,
26
- systemParameters
27
- } actions {
28
- // Get latest block
29
- @cds.odata.bindingparameter.collection
30
- function latest() returns Blocks;
31
-
32
- // Get block by height
33
- function byHeight(height: Integer) returns Blocks;
34
- };
35
-
36
- /**
37
- * Transactions with expanded relationships
38
- */
22
+ entity Blocks as
23
+ projection on midnight.Blocks {
24
+ *,
25
+ parent,
26
+ transactions,
27
+ systemParameters
28
+ }
29
+ actions {
30
+ // Get latest block
31
+ @cds.odata.bindingparameter.collection
32
+ function latest() returns Blocks;
33
+
34
+ // Get block by height
35
+ function byHeight(height: Integer) returns Blocks;
36
+
37
+ // Windowed range query for block pagination
38
+ @cds.odata.bindingparameter.collection
39
+ function range(startHeight: Integer64, endHeight: Integer64, limit: Integer) returns array of Blocks;
40
+ };
41
+
42
+ /**
43
+ * Transactions with expanded relationships
44
+ */
39
45
  @readonly
40
- entity Transactions as projection on midnight.Transactions {
41
- *,
42
- block,
43
- transactionResult,
44
- transactionFees,
45
- contractActions,
46
- unshieldedCreatedOutputs,
47
- unshieldedSpentOutputs,
48
- zswapLedgerEvents,
49
- dustLedgerEvents
50
- } actions {
51
- // Get transaction by hash
52
- function byHash(hash: String) returns Transactions;
53
- };
46
+ entity Transactions as
47
+ projection on midnight.Transactions {
48
+ *,
49
+ block,
50
+ transactionResult,
51
+ transactionFees,
52
+ contractActions,
53
+ unshieldedCreatedOutputs,
54
+ unshieldedSpentOutputs,
55
+ zswapLedgerEvents,
56
+ dustLedgerEvents
57
+ }
58
+ actions {
59
+ // Get transaction by hash
60
+ function byHash(hash: String) returns Transactions;
61
+
62
+ // Filter transactions by classified tx type
63
+ @cds.odata.bindingparameter.collection
64
+ function byType(txType: String, limit: Integer) returns array of Transactions;
65
+ };
54
66
 
55
67
  @readonly
56
- entity TransactionResults as projection on midnight.TransactionResults;
68
+ entity TransactionResults as projection on midnight.TransactionResults;
57
69
 
58
70
  @readonly
59
- entity TransactionSegments as projection on midnight.TransactionSegments;
71
+ entity TransactionSegments as projection on midnight.TransactionSegments;
60
72
 
61
73
  @readonly
62
- entity TransactionFees as projection on midnight.TransactionFees;
74
+ entity TransactionFees as projection on midnight.TransactionFees;
63
75
 
64
76
  // ========================================================================
65
77
  // Smart Contracts
66
78
  // ========================================================================
67
79
 
68
- /**
69
- * Contract actions with deployment, call, and update tracking
70
- */
80
+ /**
81
+ * Contract actions with deployment, call, and update tracking
82
+ */
71
83
  @readonly
72
- entity ContractActions as projection on midnight.ContractActions {
73
- *,
74
- transaction,
75
- deploy,
76
- unshieldedBalances
77
- } actions {
78
- // Get contract actions by address
79
- @cds.odata.bindingparameter.collection
80
- function byAddress(address: String) returns array of ContractActions;
81
-
82
- // Get contract history
83
- function history(address: String) returns array of ContractActions;
84
- };
84
+ entity ContractActions as
85
+ projection on midnight.ContractActions {
86
+ *,
87
+ transaction,
88
+ deploy,
89
+ unshieldedBalances
90
+ }
91
+ actions {
92
+ // Get contract actions by address
93
+ @cds.odata.bindingparameter.collection
94
+ function byAddress(address: String) returns array of ContractActions;
95
+
96
+ // Get contract history
97
+ function history(address: String) returns array of ContractActions;
98
+ };
85
99
 
86
100
  @readonly
87
- entity ContractBalances as projection on midnight.ContractBalances;
101
+ entity ContractBalances as projection on midnight.ContractBalances;
88
102
 
89
103
  // ========================================================================
90
104
  // UTXOs
91
105
  // ========================================================================
92
106
 
93
- /**
94
- * Unshielded UTXOs for transparent transactions
95
- */
107
+ /**
108
+ * Unshielded UTXOs for transparent transactions
109
+ */
96
110
  @readonly
97
- entity UnshieldedUtxos as projection on midnight.UnshieldedUtxos {
98
- *,
99
- createdAtTransaction,
100
- spentAtTransaction
101
- } actions {
102
- // Get UTXOs by owner
103
- @cds.odata.bindingparameter.collection
104
- function byOwner(owner: String) returns array of UnshieldedUtxos;
105
-
106
- // Get unspent UTXOs
107
- @cds.odata.bindingparameter.collection
108
- function unspent() returns array of UnshieldedUtxos;
109
- };
111
+ entity UnshieldedUtxos as
112
+ projection on midnight.UnshieldedUtxos {
113
+ *,
114
+ createdAtTransaction,
115
+ spentAtTransaction
116
+ }
117
+ actions {
118
+ // Get UTXOs by owner
119
+ @cds.odata.bindingparameter.collection
120
+ function byOwner(owner: String) returns array of UnshieldedUtxos;
121
+
122
+ // Get unspent UTXOs
123
+ @cds.odata.bindingparameter.collection
124
+ function unspent() returns array of UnshieldedUtxos;
125
+ };
110
126
 
111
127
  // ========================================================================
112
128
  // Ledger Events
113
129
  // ========================================================================
114
130
 
115
131
  @readonly
116
- entity ZswapLedgerEvents as projection on midnight.ZswapLedgerEvents;
132
+ entity ZswapLedgerEvents as projection on midnight.ZswapLedgerEvents;
117
133
 
118
134
  @readonly
119
- entity DustLedgerEvents as projection on midnight.DustLedgerEvents;
135
+ entity DustLedgerEvents as projection on midnight.DustLedgerEvents;
120
136
 
121
137
  // ========================================================================
122
138
  // Governance & System Parameters
123
139
  // ========================================================================
124
140
 
125
- /**
126
- * Current system parameters
127
- */
141
+ /**
142
+ * Current system parameters
143
+ */
128
144
  @readonly
129
- entity SystemParameters as projection on midnight.SystemParameters actions {
130
- // Get current active parameters
131
- @cds.odata.bindingparameter.collection
132
- function current() returns SystemParameters;
133
- };
145
+ entity SystemParameters as projection on midnight.SystemParameters
146
+ actions {
147
+ // Get current active parameters
148
+ @cds.odata.bindingparameter.collection
149
+ function current() returns SystemParameters;
150
+ };
134
151
 
135
152
  /**
136
153
  * D-Parameter change history for governance tracking
137
154
  */
138
155
  @readonly
139
- entity DParameterHistory as projection on midnight.DParameterHistory {
140
- *,
141
- block
142
- };
156
+ entity DParameterHistory as
157
+ projection on midnight.DParameterHistory {
158
+ *,
159
+ block
160
+ };
143
161
 
144
162
  /**
145
163
  * Terms and Conditions history
146
164
  */
147
165
  @readonly
148
- entity TermsAndConditionsHistory as projection on midnight.TermsAndConditionsHistory {
149
- *,
150
- block
151
- };
166
+ entity TermsAndConditionsHistory as
167
+ projection on midnight.TermsAndConditionsHistory {
168
+ *,
169
+ block
170
+ };
152
171
 
153
172
  // ========================================================================
154
173
  // DUST Generation
155
174
  // ========================================================================
156
175
 
157
- /**
158
- * DUST generation status for Cardano staking rewards
159
- */
176
+ /**
177
+ * DUST generation status for Cardano staking rewards
178
+ */
160
179
  @readonly
161
- entity DustGenerationStatus as projection on midnight.DustGenerationStatus actions {
162
- // Query by Cardano reward address
163
- @cds.odata.bindingparameter.collection
164
- function byCardanoAddress(address: String) returns DustGenerationStatus;
180
+ entity DustGenerationStatus as projection on midnight.DustGenerationStatus
181
+ actions {
182
+ // Query by Cardano reward address
183
+ @cds.odata.bindingparameter.collection
184
+ function byCardanoAddress(address: String) returns DustGenerationStatus;
165
185
 
166
- // Batch query for multiple addresses
167
- @cds.odata.bindingparameter.collection
168
- function byCardanoAddresses(addresses: array of String) returns array of DustGenerationStatus;
169
- };
186
+ // Batch query for multiple addresses
187
+ @cds.odata.bindingparameter.collection
188
+ function byCardanoAddresses(addresses: array of String) returns array of DustGenerationStatus;
189
+ };
170
190
 
171
191
  // ========================================================================
172
192
  // Balance & Token Tracking
173
193
  // ========================================================================
174
194
 
175
- /**
176
- * Unshielded NIGHT token balances per address
177
- */
195
+ /**
196
+ * Unshielded NIGHT token balances per address
197
+ */
178
198
  @readonly
179
- entity NightBalances as projection on midnight.NightBalances actions {
180
- // Get balance for a specific address
181
- @cds.odata.bindingparameter.collection
182
- function getBalance(address: String) returns NightBalances;
183
-
184
- // Get top holders by balance
185
- @cds.odata.bindingparameter.collection
186
- function getTopHolders(limit: Integer) returns array of NightBalances;
187
- };
188
-
189
- /**
190
- * NIGHT ↔ DUST registration linkage
191
- */
199
+ entity NightBalances as projection on midnight.NightBalances
200
+ actions {
201
+ // Get balance for a specific address
202
+ @cds.odata.bindingparameter.collection
203
+ function getBalance(address: String) returns NightBalances;
204
+
205
+ // Get top holders by balance
206
+ @cds.odata.bindingparameter.collection
207
+ function getTopHolders(limit: Integer) returns array of NightBalances;
208
+ };
209
+
210
+ /**
211
+ * NIGHT ↔ DUST registration linkage
212
+ */
192
213
  @readonly
193
- entity DustRegistrations as projection on midnight.DustRegistrations actions {
194
- // Get registration by Cardano stake key
195
- @cds.odata.bindingparameter.collection
196
- function byCardanoStakeKey(stakeKey: String) returns DustRegistrations;
197
- };
214
+ entity DustRegistrations as projection on midnight.DustRegistrations
215
+ actions {
216
+ // Get registration by Cardano stake key
217
+ @cds.odata.bindingparameter.collection
218
+ function byCardanoStakeKey(stakeKey: String) returns DustRegistrations;
219
+ };
198
220
 
199
221
  /**
200
222
  * Token type registry
201
223
  */
202
224
  @readonly
203
- entity TokenTypes as projection on midnight.TokenTypes;
225
+ entity TokenTypes as projection on midnight.TokenTypes;
204
226
 
205
227
  // ========================================================================
206
228
  // Session Management (Wallet Connections)
@@ -209,14 +231,50 @@ service NightgateService {
209
231
  /**
210
232
  * Wallet session management
211
233
  */
212
- entity WalletSessions as projection on midnight.WalletSessions excluding {
213
- viewingKeyHash, // Internal lookup field
214
- encryptedViewingKey // Encrypted key — never exposed via OData
215
- } actions {
216
- // Connect with viewing key
217
- action connectWallet(viewingKey: String) returns WalletSessions;
218
-
219
- // Disconnect session
220
- action disconnectWallet(sessionId: UUID);
221
- };
234
+ entity WalletSessions as
235
+ projection on midnight.WalletSessions
236
+ excluding {
237
+ viewingKeyHash, // Internal lookup field
238
+ encryptedViewingKey // Encrypted key never exposed via OData
239
+ }
240
+ actions {
241
+ // Connect with viewing key
242
+ action connectWallet(viewingKey: String) returns WalletSessions;
243
+
244
+ // Disconnect session
245
+ action disconnectWallet(sessionId: UUID);
246
+ };
222
247
  }
248
+
249
+ // ============================================================================
250
+ // Service-Level Annotations
251
+ // ============================================================================
252
+
253
+ annotate NightgateService.Blocks with {
254
+ hash @title: 'Block Hash';
255
+ height @title: 'Block Height';
256
+ };
257
+
258
+ annotate NightgateService.Transactions with {
259
+ hash @title: 'Transaction Hash';
260
+ };
261
+
262
+ annotate NightgateService.ContractActions with {
263
+ address @title: 'Contract Address';
264
+ };
265
+
266
+ annotate NightgateService.DustGenerationStatus with {
267
+ cardanoRewardAddress @title: 'Cardano Reward Address';
268
+ nightBalance @title: 'NIGHT Balance';
269
+ generationRate @title: 'Generation Rate';
270
+ };
271
+
272
+ annotate NightgateService.NightBalances with {
273
+ address @title: 'Address';
274
+ balance @title: 'NIGHT Balance';
275
+ };
276
+
277
+ annotate NightgateService.TokenTypes with {
278
+ tokenTypeId @title: 'Token Type ID';
279
+ tokenName @title: 'Token Name';
280
+ };
@@ -35,6 +35,23 @@ class NightgateService extends cds_1.default.ApplicationService {
35
35
  return req.reject(400, 'height is required');
36
36
  return this.db.run(cds_1.default.ql.SELECT.one.from('midnight.Blocks').where({ height }));
37
37
  });
38
+ this.on('range', 'Blocks', async (req) => {
39
+ const { startHeight, endHeight, limit } = req.data;
40
+ if (startHeight == null || endHeight == null) {
41
+ return req.reject(400, 'startHeight and endHeight are required');
42
+ }
43
+ if (!Number.isInteger(startHeight) || !Number.isInteger(endHeight) || startHeight < 0 || endHeight < 0) {
44
+ return req.reject(400, 'startHeight and endHeight must be non-negative integers');
45
+ }
46
+ if (endHeight < startHeight) {
47
+ return req.reject(400, 'endHeight must be greater than or equal to startHeight');
48
+ }
49
+ const effectiveLimit = Math.min(Math.max(limit || 100, 1), 5000);
50
+ return this.db.run(cds_1.default.ql.SELECT.from('midnight.Blocks')
51
+ .where({ height: { '>=': startHeight, '<=': endHeight } })
52
+ .orderBy('height asc')
53
+ .limit(effectiveLimit));
54
+ });
38
55
  // ====================================================================
39
56
  // Transaction Handlers
40
57
  // ====================================================================
@@ -47,6 +64,16 @@ class NightgateService extends cds_1.default.ApplicationService {
47
64
  return req.reject(400, 'hash is required');
48
65
  return this.db.run(cds_1.default.ql.SELECT.from('midnight.Transactions').where({ hash }));
49
66
  });
67
+ this.on('byType', 'Transactions', async (req) => {
68
+ const { txType, limit } = req.data;
69
+ if (!txType)
70
+ return req.reject(400, 'txType is required');
71
+ const effectiveLimit = Math.min(Math.max(limit || 100, 1), 2000);
72
+ return this.db.run(cds_1.default.ql.SELECT.from('midnight.Transactions')
73
+ .where({ txType })
74
+ .orderBy('createdAt desc')
75
+ .limit(effectiveLimit));
76
+ });
50
77
  // ====================================================================
51
78
  // Contract Handlers
52
79
  // ====================================================================
@@ -175,7 +175,19 @@ class MidnightNodeProvider {
175
175
  if (message.method && message.params?.subscription) {
176
176
  const callback = this.subscriptions.get(message.params.subscription);
177
177
  if (callback) {
178
- callback(message.params.result);
178
+ try {
179
+ const callbackResult = callback(message.params.result);
180
+ if (callbackResult && typeof callbackResult.then === 'function') {
181
+ void callbackResult.catch((err) => {
182
+ const errMsg = err instanceof Error ? err.message : String(err);
183
+ console.error('[MidnightNode] Subscription callback failed:', errMsg);
184
+ });
185
+ }
186
+ }
187
+ catch (err) {
188
+ const errMsg = err instanceof Error ? err.message : String(err);
189
+ console.error('[MidnightNode] Subscription callback failed:', errMsg);
190
+ }
179
191
  }
180
192
  return;
181
193
  }
@@ -1,40 +1,41 @@
1
1
  /**
2
2
  * Minimal SCALE codec helpers for Substrate extrinsic parsing.
3
3
  *
4
- * Only implements what's needed to extract pallet_index + call_index
5
- * from hex-encoded extrinsics. No external dependencies.
4
+ * Includes:
5
+ * - compact integer decoding
6
+ * - pallet/call extraction
7
+ * - signed participant extraction (sender/receiver/amount for transfer-like calls)
6
8
  */
9
+ export interface ExtrinsicParticipantInfo {
10
+ isSigned: boolean;
11
+ palletIndex: number;
12
+ callIndex: number;
13
+ senderAddress?: string;
14
+ receiverAddress?: string;
15
+ amount?: string;
16
+ }
7
17
  /**
8
- * Decode a SCALE compact-encoded unsigned integer.
18
+ * Decode a SCALE compact-encoded unsigned integer as bigint.
9
19
  * Returns [value, bytesConsumed] or null if buffer too short.
10
- *
11
- * Compact encoding modes (2 LSBs of first byte):
12
- * 0b00 → single-byte mode: value = byte >> 2 (0..63)
13
- * 0b01 two-byte mode: value = u16 >> 2 (64..16383)
14
- * 0b10 four-byte mode: value = u32 >> 2 (16384..2^30-1)
15
- * 0b11 → big-integer mode: upper 6 bits = extra bytes (not supported here)
20
+ */
21
+ export declare function decodeCompactBigInt(buf: Buffer, offset: number): [bigint, number] | null;
22
+ /**
23
+ * Compatibility wrapper for existing callers that expect number values.
24
+ * Values above Number.MAX_SAFE_INTEGER are returned as 0 while preserving bytesConsumed.
16
25
  */
17
26
  export declare function decodeCompact(buf: Buffer, offset: number): [number, number] | null;
18
27
  /**
19
28
  * Parse a hex-encoded Substrate extrinsic to extract pallet_index and call_index.
20
- *
21
- * Extrinsic structure:
22
- * [compact_length][version_byte][...payload...]
23
- *
24
- * Version byte:
25
- * bit 7 = signed flag (0x84 = signed v4, 0x04 = unsigned v4)
26
- *
27
- * Unsigned payload:
28
- * [pallet_index: u8][call_index: u8][args...]
29
- *
30
- * Signed payload:
31
- * [address_type: u8][account_id: 32B][sig_type: u8][signature: 64B]
32
- * [era: 1-2B][nonce: compact][tip: compact]
33
- * [pallet_index: u8][call_index: u8][args...]
34
- *
35
29
  * Returns null on parse failure (safe fallback to existing heuristics).
36
30
  */
37
31
  export declare function parseExtrinsicCallIndices(hex: string): {
38
32
  palletIndex: number;
39
33
  callIndex: number;
40
34
  } | null;
35
+ /**
36
+ * Extract signed sender + first transfer-style destination/amount, if present.
37
+ *
38
+ * This parser is intentionally conservative: receiver/amount are set only when
39
+ * the first call args decode as MultiAddress + Compact<Balance>.
40
+ */
41
+ export declare function parseExtrinsicParticipantInfo(hex: string): ExtrinsicParticipantInfo | null;