@lerna-labs/hydra-sdk 1.0.0-beta.13 → 1.0.0-beta.14
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 +159 -0
- package/dist/cache/disk-cache.d.ts +37 -0
- package/dist/cache/disk-cache.js +84 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +12 -0
- package/dist/hydra/messages.d.ts +31 -0
- package/dist/hydra/messages.js +1 -0
- package/dist/hydra/utxo.d.ts +13 -3
- package/dist/hydra/utxo.js +21 -20
- package/dist/index.d.ts +10 -3
- package/dist/index.js +5 -2
- package/dist/ipfs/ipfs.d.ts +22 -0
- package/dist/ipfs/ipfs.js +77 -0
- package/dist/mesh/get-admin.d.ts +9 -0
- package/dist/mesh/get-admin.js +25 -4
- package/dist/mesh/native-script.d.ts +15 -6
- package/dist/mesh/native-script.js +18 -15
- package/dist/mesh/wrangler.d.ts +99 -11
- package/dist/mesh/wrangler.js +240 -193
- package/dist/test.js +2 -2
- package/dist/tx3/submit-tx.d.ts +8 -0
- package/dist/tx3/submit-tx.js +8 -0
- package/dist/utils/chunk-string.d.ts +7 -0
- package/dist/utils/chunk-string.js +7 -0
- package/dist/utils/verify-signature.d.ts +13 -5
- package/dist/utils/verify-signature.js +21 -16
- package/package.json +15 -4
package/dist/mesh/wrangler.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { BlockfrostProvider } from '@meshsdk/core';
|
|
2
|
+
import { HydraInstance, HydraProvider } from '@meshsdk/hydra';
|
|
3
|
+
import { requireEnv } from '../config.js';
|
|
4
|
+
/**
|
|
5
|
+
* High-level controller for Hydra head lifecycle operations.
|
|
6
|
+
*
|
|
7
|
+
* Wraps `HydraProvider` and `HydraInstance` to provide a simplified API
|
|
8
|
+
* for initializing, opening, and closing a Hydra head.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const wrangler = new Wrangler("http://localhost:4001");
|
|
13
|
+
* await wrangler.waitForHeadOpen({
|
|
14
|
+
* utxos: [{ txHash: "abc...", outputIndex: 0 }],
|
|
15
|
+
* blueprintTx: { type: "Tx ConwayEra", cborHex: "...", description: "" },
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
6
19
|
export class Wrangler {
|
|
7
|
-
BLOCKFROST_KEY;
|
|
8
20
|
mode;
|
|
9
21
|
provider;
|
|
10
22
|
instance;
|
|
@@ -12,10 +24,9 @@ export class Wrangler {
|
|
|
12
24
|
url;
|
|
13
25
|
wsUrl;
|
|
14
26
|
constructor(url, wsUrl) {
|
|
15
|
-
this.url = url ||
|
|
16
|
-
this.wsUrl = wsUrl ||
|
|
17
|
-
this.
|
|
18
|
-
this.blockfrost = new BlockfrostProvider(this.BLOCKFROST_KEY);
|
|
27
|
+
this.url = url || requireEnv('HYDRA_API_URL');
|
|
28
|
+
this.wsUrl = wsUrl || requireEnv('HYDRA_WS_URL');
|
|
29
|
+
this.blockfrost = new BlockfrostProvider(requireEnv('BLOCKFROST_API_KEY'));
|
|
19
30
|
this.provider = this.createHydraProvider();
|
|
20
31
|
this.instance = this.createHydraInstance();
|
|
21
32
|
}
|
|
@@ -29,207 +40,243 @@ export class Wrangler {
|
|
|
29
40
|
submitter: this.provider,
|
|
30
41
|
});
|
|
31
42
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
async waitForHeadClose(timeoutMs) {
|
|
46
|
-
this.mode = "shutdown";
|
|
47
|
-
return new Promise(async (resolve, reject) => {
|
|
48
|
-
let settled = false;
|
|
49
|
-
const handle = async (message) => {
|
|
50
|
-
try {
|
|
51
|
-
console.log("Message received: ", message.tag, message);
|
|
52
|
-
switch (message.tag) {
|
|
53
|
-
case "HeadIsClosed":
|
|
54
|
-
case "HeadIsFinalized":
|
|
55
|
-
if (settled)
|
|
56
|
-
return;
|
|
57
|
-
settled = true;
|
|
58
|
-
resolve();
|
|
59
|
-
break;
|
|
60
|
-
case "ReadyToFanout":
|
|
61
|
-
if (settled)
|
|
62
|
-
return;
|
|
63
|
-
await this.provider.fanout();
|
|
64
|
-
break;
|
|
65
|
-
case "Greetings":
|
|
66
|
-
await this.onGreetings(message.headStatus);
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
if (!settled) {
|
|
72
|
-
settled = true;
|
|
73
|
-
reject(err);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
this.provider.onMessage(handle);
|
|
43
|
+
/**
|
|
44
|
+
* Connect to the Hydra node with exponential-backoff retry.
|
|
45
|
+
*
|
|
46
|
+
* Uses `HydraProvider.isConnected()` which establishes the WebSocket
|
|
47
|
+
* **and** waits for the Hydra `Greetings` handshake, unlike the raw
|
|
48
|
+
* `connect()` which only opens the socket.
|
|
49
|
+
*
|
|
50
|
+
* @param maxAttempts - Maximum number of connection attempts (default 5).
|
|
51
|
+
* @param baseDelayMs - Initial retry delay in milliseconds (default 1000). Doubles each attempt, capped at 30 s.
|
|
52
|
+
*/
|
|
53
|
+
async connectWithRetry(maxAttempts = 5, baseDelayMs = 1000) {
|
|
54
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
78
55
|
try {
|
|
79
|
-
await this.provider.
|
|
56
|
+
const connected = await this.provider.isConnected();
|
|
57
|
+
if (connected)
|
|
58
|
+
return;
|
|
59
|
+
throw new Error('isConnected() returned false');
|
|
80
60
|
}
|
|
81
61
|
catch (err) {
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
return reject(new Error("Failed to connect to Hydra provider: " + String(err)));
|
|
62
|
+
if (attempt === maxAttempts - 1) {
|
|
63
|
+
throw new Error(`Failed to connect after ${maxAttempts} attempts: ${String(err)}`);
|
|
85
64
|
}
|
|
65
|
+
const delay = Math.min(baseDelayMs * 2 ** attempt, 30_000);
|
|
66
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
86
67
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
origReject(e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Shared helper for promise-based methods that wait for a specific
|
|
72
|
+
* Hydra message. Handles connection, timeout, and settlement in one place.
|
|
73
|
+
*/
|
|
74
|
+
awaitMessage(handler, timeoutMs, timeoutMessage) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
let settled = false;
|
|
77
|
+
const settle = (fn, value) => {
|
|
78
|
+
if (settled)
|
|
79
|
+
return;
|
|
80
|
+
settled = true;
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
fn(value);
|
|
103
83
|
};
|
|
84
|
+
this.provider.onMessage((message) => {
|
|
85
|
+
handler(message, (value) => settle(resolve, value), (reason) => settle(reject, reason));
|
|
86
|
+
});
|
|
87
|
+
const timer = setTimeout(() => settle(reject, new Error(timeoutMessage)), timeoutMs);
|
|
88
|
+
this.connectWithRetry().catch((err) => settle(reject, new Error(`Failed to connect: ${String(err)}`)));
|
|
104
89
|
});
|
|
105
90
|
}
|
|
91
|
+
/** Connect the underlying HydraProvider WebSocket with retry logic. */
|
|
92
|
+
async connect() {
|
|
93
|
+
return await this.connectWithRetry();
|
|
94
|
+
}
|
|
95
|
+
/** Disconnect the underlying HydraProvider WebSocket. */
|
|
96
|
+
async disconnect(timeout) {
|
|
97
|
+
return this.provider.disconnect(timeout);
|
|
98
|
+
}
|
|
99
|
+
/** Return the current HydraProvider connection/head status. */
|
|
100
|
+
getStatus() {
|
|
101
|
+
return this.provider.getStatus();
|
|
102
|
+
}
|
|
103
|
+
/** Register a callback for HydraProvider status changes. */
|
|
104
|
+
onStatusChange(callback) {
|
|
105
|
+
return this.provider.onStatusChange(callback);
|
|
106
|
+
}
|
|
107
|
+
/** Begin the head-opening sequence: init, commit, and listen for state changes. */
|
|
108
|
+
async startHead(commitArgs) {
|
|
109
|
+
this.mode = 'start';
|
|
110
|
+
this.provider.onMessage((msg) => this.handleIncoming(msg, commitArgs));
|
|
111
|
+
await this.connectWithRetry();
|
|
112
|
+
}
|
|
113
|
+
/** Begin the head-closing sequence: close, fanout, and finalize. */
|
|
114
|
+
async shutdownHead() {
|
|
115
|
+
this.mode = 'shutdown';
|
|
116
|
+
this.provider.onMessage((msg) => this.handleIncoming(msg));
|
|
117
|
+
await this.connectWithRetry();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Wait for the Hydra head to fully close and finalize.
|
|
121
|
+
* @param timeoutMs - Maximum time to wait in milliseconds.
|
|
122
|
+
*/
|
|
123
|
+
async waitForHeadClose(timeoutMs = 180000) {
|
|
124
|
+
this.mode = 'shutdown';
|
|
125
|
+
return this.awaitMessage((message, resolve, reject) => {
|
|
126
|
+
switch (message.tag) {
|
|
127
|
+
case 'HeadIsClosed':
|
|
128
|
+
case 'HeadIsFinalized':
|
|
129
|
+
resolve();
|
|
130
|
+
break;
|
|
131
|
+
case 'ReadyToFanout':
|
|
132
|
+
this.provider.fanout();
|
|
133
|
+
break;
|
|
134
|
+
case 'Greetings':
|
|
135
|
+
this.onGreetings(message.headStatus).catch((err) => reject(new Error(`Greetings handler failed: ${String(err)}`)));
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}, timeoutMs, 'Timeout waiting for head to close!');
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Wait for the Hydra head to reach the `Open` state.
|
|
142
|
+
* @param commitArgs - UTxO to commit into the head during initialization.
|
|
143
|
+
* @param timeoutMs - Maximum time to wait in milliseconds.
|
|
144
|
+
*/
|
|
106
145
|
async waitForHeadOpen(commitArgs, timeoutMs = 180000) {
|
|
107
|
-
this.mode =
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
if (message.tag === "HeadIsOpen") {
|
|
113
|
-
if (settled)
|
|
114
|
-
return;
|
|
115
|
-
settled = true;
|
|
116
|
-
resolve();
|
|
117
|
-
}
|
|
118
|
-
else if (message.tag === "HeadIsInitializing") {
|
|
119
|
-
if (!commitArgs)
|
|
120
|
-
return;
|
|
121
|
-
await this.doCommit(commitArgs);
|
|
122
|
-
}
|
|
123
|
-
else if (message.tag === "Greetings") {
|
|
124
|
-
await this.onGreetings(message.headStatus, commitArgs);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
if (!settled) {
|
|
129
|
-
settled = true;
|
|
130
|
-
reject(err);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
this.provider.onMessage(handle);
|
|
135
|
-
try {
|
|
136
|
-
await this.provider.connect();
|
|
146
|
+
this.mode = 'start';
|
|
147
|
+
return this.awaitMessage((message, resolve, reject) => {
|
|
148
|
+
if (message.tag === 'HeadIsOpen') {
|
|
149
|
+
resolve();
|
|
137
150
|
}
|
|
138
|
-
|
|
139
|
-
if (!
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
151
|
+
else if (message.tag === 'HeadIsInitializing') {
|
|
152
|
+
if (!commitArgs)
|
|
153
|
+
return;
|
|
154
|
+
this.doCommit(commitArgs).catch((err) => reject(new Error(`Commit failed: ${String(err)}`)));
|
|
143
155
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
}, timeoutMs);
|
|
150
|
-
const finalizer = () => clearTimeout(timer);
|
|
151
|
-
const origResolve = resolve;
|
|
152
|
-
const origReject = reject;
|
|
153
|
-
resolve = (v) => {
|
|
154
|
-
finalizer();
|
|
155
|
-
origResolve(v);
|
|
156
|
-
};
|
|
157
|
-
reject = (e) => {
|
|
158
|
-
finalizer();
|
|
159
|
-
origReject(e);
|
|
160
|
-
};
|
|
161
|
-
});
|
|
156
|
+
else if (message.tag === 'Greetings') {
|
|
157
|
+
this.onGreetings(message.headStatus, commitArgs).catch((err) => reject(new Error(`Greetings handler failed: ${String(err)}`)));
|
|
158
|
+
}
|
|
159
|
+
}, timeoutMs, 'Timeout waiting for head to open');
|
|
162
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Query the current Hydra head status via a `Greetings` message.
|
|
163
|
+
* @param timeoutMs - Maximum time to wait for the status response.
|
|
164
|
+
* @returns The head status string (e.g. `"Idle"`, `"Open"`, `"Closed"`).
|
|
165
|
+
*/
|
|
163
166
|
async getHeadStatus(timeoutMs = 5000) {
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (settled)
|
|
168
|
-
return;
|
|
169
|
-
if (message.tag === "Greetings") {
|
|
170
|
-
settled = true;
|
|
171
|
-
resolve(message.headStatus);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
this.provider.onMessage(handle);
|
|
175
|
-
try {
|
|
176
|
-
await this.provider.connect();
|
|
167
|
+
return this.awaitMessage((message, resolve, _reject) => {
|
|
168
|
+
if (message.tag === 'Greetings') {
|
|
169
|
+
resolve(message.headStatus);
|
|
177
170
|
}
|
|
178
|
-
|
|
179
|
-
if (!settled) {
|
|
180
|
-
settled = true;
|
|
181
|
-
return reject(new Error("Failed to connect to Hydra provider: " + String(err)));
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const timer = setTimeout(() => {
|
|
185
|
-
if (!settled) {
|
|
186
|
-
settled = true;
|
|
187
|
-
reject(new Error("Timeout waiting for head to open"));
|
|
188
|
-
}
|
|
189
|
-
}, timeoutMs);
|
|
190
|
-
const finalizer = () => clearTimeout(timer);
|
|
191
|
-
const origResolve = resolve;
|
|
192
|
-
const origReject = reject;
|
|
193
|
-
resolve = (v) => {
|
|
194
|
-
finalizer();
|
|
195
|
-
origResolve(v);
|
|
196
|
-
};
|
|
197
|
-
reject = (e) => {
|
|
198
|
-
finalizer();
|
|
199
|
-
origReject(e);
|
|
200
|
-
};
|
|
201
|
-
});
|
|
171
|
+
}, timeoutMs, 'Timeout waiting for head status');
|
|
202
172
|
}
|
|
203
173
|
async doCommit(commitArgs) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
174
|
+
let rawTx;
|
|
175
|
+
if (commitArgs.blueprintTx) {
|
|
176
|
+
rawTx = await this.instance.commitBlueprintUTxOs(commitArgs.utxos, commitArgs.blueprintTx);
|
|
177
|
+
}
|
|
178
|
+
else if (commitArgs.utxos.length === 0) {
|
|
179
|
+
rawTx = await this.instance.commitEmpty();
|
|
180
|
+
}
|
|
181
|
+
else if (commitArgs.utxos.length === 1) {
|
|
182
|
+
const { txHash, outputIndex } = commitArgs.utxos[0];
|
|
183
|
+
rawTx = await this.instance.commitFunds(txHash, outputIndex);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
throw new Error('Multiple UTxOs without a blueprintTx require a blueprint transaction');
|
|
187
|
+
}
|
|
188
|
+
return await this.blockfrost.submitTx(rawTx);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Decommit funds from an open Hydra head back to L1.
|
|
192
|
+
*
|
|
193
|
+
* Posts the decommit transaction via `provider.publishDecommit()` (HTTP POST)
|
|
194
|
+
* instead of `provider.decommit()` to avoid overwriting the Wrangler's
|
|
195
|
+
* `onMessage` handler (single-callback replacement pattern).
|
|
196
|
+
*
|
|
197
|
+
* Resolves on `DecommitApproved` — L1 settlement happens asynchronously.
|
|
198
|
+
*
|
|
199
|
+
* @param transaction - The decommit transaction (CBOR-encoded).
|
|
200
|
+
* @param timeoutMs - Maximum time to wait for approval (default 60s).
|
|
201
|
+
*/
|
|
202
|
+
async decommit(transaction, timeoutMs = 60000) {
|
|
203
|
+
const status = await this.getHeadStatus();
|
|
204
|
+
if (status !== 'Open') {
|
|
205
|
+
throw new Error(`Cannot decommit: head is "${status}", expected "Open"`);
|
|
206
|
+
}
|
|
207
|
+
const result = this.awaitMessage((message, resolve, reject) => {
|
|
208
|
+
if (message.tag === 'DecommitApproved') {
|
|
209
|
+
resolve();
|
|
210
|
+
}
|
|
211
|
+
else if (message.tag === 'DecommitInvalid') {
|
|
212
|
+
reject(new Error(`Decommit invalid: ${JSON.stringify(message.decommitInvalidReason)}`));
|
|
213
|
+
}
|
|
214
|
+
}, timeoutMs, 'Timeout waiting for decommit approval');
|
|
215
|
+
await this.provider.publishDecommit(transaction);
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Incrementally commit funds into an already-open Hydra head.
|
|
220
|
+
*
|
|
221
|
+
* Only single-UTxO commits are supported (MeshJS limitation).
|
|
222
|
+
* The raw L1 transaction is submitted to Blockfrost automatically.
|
|
223
|
+
*
|
|
224
|
+
* Resolves on `CommitFinalized`.
|
|
225
|
+
*
|
|
226
|
+
* @param commitArgs - Single UTxO (with optional blueprint) to commit.
|
|
227
|
+
* @param timeoutMs - Maximum time to wait for finalization (default 120s).
|
|
228
|
+
*/
|
|
229
|
+
async incrementalCommit(commitArgs, timeoutMs = 120000) {
|
|
230
|
+
const status = await this.getHeadStatus();
|
|
231
|
+
if (status !== 'Open') {
|
|
232
|
+
throw new Error(`Cannot incrementally commit: head is "${status}", expected "Open"`);
|
|
233
|
+
}
|
|
234
|
+
await this.doIncrementalCommit(commitArgs);
|
|
235
|
+
return this.awaitMessage((message, resolve, _reject) => {
|
|
236
|
+
if (message.tag === 'CommitFinalized') {
|
|
237
|
+
resolve();
|
|
238
|
+
}
|
|
239
|
+
}, timeoutMs, 'Timeout waiting for incremental commit finalization');
|
|
240
|
+
}
|
|
241
|
+
async doIncrementalCommit(commitArgs) {
|
|
242
|
+
if (commitArgs.utxos.length !== 1) {
|
|
243
|
+
throw new Error('Incremental commit requires exactly one UTxO');
|
|
207
244
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
245
|
+
const { txHash, outputIndex } = commitArgs.utxos[0];
|
|
246
|
+
let rawTx;
|
|
247
|
+
if (commitArgs.blueprintTx) {
|
|
248
|
+
rawTx = await this.instance.incrementalBlueprintCommit(txHash, outputIndex, commitArgs.blueprintTx);
|
|
211
249
|
}
|
|
250
|
+
else {
|
|
251
|
+
rawTx = await this.instance.incrementalCommitFunds(txHash, outputIndex);
|
|
252
|
+
}
|
|
253
|
+
return await this.blockfrost.submitTx(rawTx);
|
|
212
254
|
}
|
|
213
255
|
async handleIncoming(message, commitArgs) {
|
|
214
|
-
if (message.tag ===
|
|
256
|
+
if (message.tag === 'Greetings') {
|
|
215
257
|
await this.onGreetings(message.headStatus, commitArgs);
|
|
216
258
|
}
|
|
217
259
|
else {
|
|
218
260
|
switch (this.mode) {
|
|
219
|
-
case
|
|
220
|
-
if (message.tag ===
|
|
261
|
+
case 'start':
|
|
262
|
+
if (message.tag === 'HeadIsInitializing') {
|
|
221
263
|
if (commitArgs === undefined) {
|
|
222
|
-
console.error(
|
|
264
|
+
console.error('No commit arguments specified... aborting commit!');
|
|
223
265
|
return;
|
|
224
266
|
}
|
|
225
|
-
|
|
267
|
+
try {
|
|
268
|
+
await this.doCommit(commitArgs);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
console.error('Commit failed during startHead:', err);
|
|
272
|
+
}
|
|
226
273
|
}
|
|
227
|
-
if (message.tag ===
|
|
274
|
+
if (message.tag === 'HeadIsOpen') {
|
|
228
275
|
// Successfully started the head here... close gracefully?
|
|
229
276
|
}
|
|
230
277
|
break;
|
|
231
|
-
case
|
|
232
|
-
if (message.tag ===
|
|
278
|
+
case 'shutdown':
|
|
279
|
+
if (message.tag === 'ReadyToFanout') {
|
|
233
280
|
await this.provider.fanout();
|
|
234
281
|
}
|
|
235
282
|
break;
|
|
@@ -238,35 +285,35 @@ export class Wrangler {
|
|
|
238
285
|
}
|
|
239
286
|
async onGreetings(status, commitArgs) {
|
|
240
287
|
switch (this.mode) {
|
|
241
|
-
case
|
|
288
|
+
case 'start':
|
|
242
289
|
switch (status) {
|
|
243
|
-
case
|
|
244
|
-
console.log(
|
|
290
|
+
case 'Idle':
|
|
291
|
+
console.log('Idle → init()');
|
|
245
292
|
await this.provider.init();
|
|
246
293
|
break;
|
|
247
|
-
case
|
|
248
|
-
console.log(
|
|
294
|
+
case 'Initializing':
|
|
295
|
+
console.log('Initializing -> commit()');
|
|
249
296
|
if (commitArgs === undefined) {
|
|
250
|
-
console.error(
|
|
297
|
+
console.error('No commit arguments specified... aborting commit!');
|
|
251
298
|
return;
|
|
252
299
|
}
|
|
253
300
|
await this.doCommit(commitArgs);
|
|
254
301
|
break;
|
|
255
|
-
case
|
|
256
|
-
console.log(
|
|
302
|
+
case 'Open':
|
|
303
|
+
console.log('Open → already ready, proceeding');
|
|
257
304
|
break;
|
|
258
305
|
default:
|
|
259
306
|
console.log(`Greetings in start mode, ignoring status: ${status}`);
|
|
260
307
|
}
|
|
261
308
|
break;
|
|
262
|
-
case
|
|
309
|
+
case 'shutdown':
|
|
263
310
|
switch (status) {
|
|
264
|
-
case
|
|
265
|
-
console.log(
|
|
311
|
+
case 'Open':
|
|
312
|
+
console.log('Shutting down: closing head…');
|
|
266
313
|
await this.provider.close();
|
|
267
314
|
break;
|
|
268
|
-
case
|
|
269
|
-
console.log(
|
|
315
|
+
case 'FanoutPossible':
|
|
316
|
+
console.log('Fanout now possible: fanning out…');
|
|
270
317
|
await this.provider.fanout();
|
|
271
318
|
break;
|
|
272
319
|
default:
|
package/dist/test.js
CHANGED
|
@@ -14,8 +14,8 @@ import { Wrangler } from './mesh/wrangler';
|
|
|
14
14
|
const admin_address = admin_wallet.addresses.enterpriseAddressBech32;
|
|
15
15
|
console.log(`Admin address: ${admin_address}`);
|
|
16
16
|
const blockfrostProvider = new BlockfrostProvider(process.env.BLOCKFROST_API_KEY);
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const _txHash = 'a000003f633d9b2efcc18dedefaf60623e3132c8b05a5751ac08d3bf6f505d54';
|
|
18
|
+
const _txIndex = 3;
|
|
19
19
|
const utxo = await blockfrostProvider.fetchAddressUTxOs(admin_address);
|
|
20
20
|
if (utxo.length < 3) {
|
|
21
21
|
console.log(utxo);
|
package/dist/tx3/submit-tx.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit a transaction to a TRP endpoint via JSON-RPC.
|
|
3
|
+
*
|
|
4
|
+
* @param submit_endpoint - URL of the TRP submit endpoint.
|
|
5
|
+
* @param payload - Hex-encoded transaction payload.
|
|
6
|
+
* @param id - JSON-RPC request identifier.
|
|
7
|
+
* @returns The fetch Response from the TRP endpoint.
|
|
8
|
+
*/
|
|
1
9
|
export declare function submitTx(submit_endpoint: string, payload: string, id: string): Promise<Response>;
|
package/dist/tx3/submit-tx.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit a transaction to a TRP endpoint via JSON-RPC.
|
|
3
|
+
*
|
|
4
|
+
* @param submit_endpoint - URL of the TRP submit endpoint.
|
|
5
|
+
* @param payload - Hex-encoded transaction payload.
|
|
6
|
+
* @param id - JSON-RPC request identifier.
|
|
7
|
+
* @returns The fetch Response from the TRP endpoint.
|
|
8
|
+
*/
|
|
1
9
|
export async function submitTx(submit_endpoint, payload, id) {
|
|
2
10
|
return await fetch(submit_endpoint, {
|
|
3
11
|
method: 'POST',
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a string into fixed-size chunks.
|
|
3
|
+
*
|
|
4
|
+
* @param str - The string to split.
|
|
5
|
+
* @param size - Maximum character count per chunk.
|
|
6
|
+
* @returns Array of string chunks.
|
|
7
|
+
*/
|
|
1
8
|
export function chunkString(str, size) {
|
|
2
9
|
const chunks = [];
|
|
3
10
|
for (let i = 0; i < str.length; i += size) {
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
1
|
+
/** Convert a buffer-like value to a hex string. */
|
|
2
|
+
export declare const bufferToHex: (buffer: Uint8Array | string) => string;
|
|
3
|
+
/** Convert a buffer-like value to an ASCII string. */
|
|
4
|
+
export declare const bufferToAscii: (buffer: Uint8Array | string) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Verify a CIP-30 COSE_Sign1 signature against an expected message and address.
|
|
7
|
+
*
|
|
8
|
+
* @param signature - Hex-encoded COSE_Sign1 signature bytes.
|
|
9
|
+
* @param message - The original plaintext message that was signed.
|
|
10
|
+
* @param signingAddress - Bech32 address of the expected signer.
|
|
11
|
+
* @param signatureKey - Hex-encoded COSE key containing the public key.
|
|
12
|
+
* @returns Validation result with `isValid`, chunked signature metadata, and public key hex.
|
|
13
|
+
*/
|
|
3
14
|
export declare function verifySignature(signature: string, message: string, signingAddress: string, signatureKey: string): {
|
|
4
15
|
isValid: boolean;
|
|
5
16
|
sigMeta: string[];
|
|
6
17
|
pubKeyHex: string;
|
|
7
18
|
};
|
|
8
|
-
/**
|
|
9
|
-
* End Signature Validation Stuff
|
|
10
|
-
*/
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { COSESign1 } from '@emurgo/cardano-message-signing-nodejs';
|
|
3
|
+
import * as CSL from '@emurgo/cardano-serialization-lib-nodejs';
|
|
4
|
+
import { bech32 } from 'bech32';
|
|
5
|
+
import { default as cbor } from 'cbor';
|
|
6
|
+
import { chunkString } from './chunk-string.js';
|
|
7
|
+
/** Convert a buffer-like value to a hex string. */
|
|
8
|
+
export const bufferToHex = (buffer) => Buffer.from(buffer).toString('hex');
|
|
9
|
+
/** Convert a buffer-like value to an ASCII string. */
|
|
10
|
+
export const bufferToAscii = (buffer) => Buffer.from(buffer).toString('ascii');
|
|
1
11
|
/**
|
|
2
|
-
*
|
|
12
|
+
* Verify a CIP-30 COSE_Sign1 signature against an expected message and address.
|
|
13
|
+
*
|
|
14
|
+
* @param signature - Hex-encoded COSE_Sign1 signature bytes.
|
|
15
|
+
* @param message - The original plaintext message that was signed.
|
|
16
|
+
* @param signingAddress - Bech32 address of the expected signer.
|
|
17
|
+
* @param signatureKey - Hex-encoded COSE key containing the public key.
|
|
18
|
+
* @returns Validation result with `isValid`, chunked signature metadata, and public key hex.
|
|
3
19
|
*/
|
|
4
|
-
import * as CSL from "@emurgo/cardano-serialization-lib-nodejs";
|
|
5
|
-
import { COSESign1 } from "@emurgo/cardano-message-signing-nodejs";
|
|
6
|
-
import { Buffer } from "buffer";
|
|
7
|
-
import { default as cbor } from "cbor";
|
|
8
|
-
import { chunkString } from "./chunk-string.js";
|
|
9
|
-
import { bech32 } from "bech32";
|
|
10
|
-
export const bufferToHex = (buffer) => Buffer.from(buffer).toString("hex");
|
|
11
|
-
export const bufferToAscii = (buffer) => Buffer.from(buffer).toString("ascii");
|
|
12
20
|
export function verifySignature(signature, message, signingAddress, signatureKey) {
|
|
13
21
|
try {
|
|
14
|
-
const coseSign1 = COSESign1.from_bytes(Buffer.from(signature,
|
|
22
|
+
const coseSign1 = COSESign1.from_bytes(Buffer.from(signature, 'hex'));
|
|
15
23
|
const signatureBytes = coseSign1.signature();
|
|
16
24
|
const [, , , payload1] = cbor.decode(bufferToHex(coseSign1.signed_data().to_bytes()));
|
|
17
25
|
const signaturePayloadAscii = bufferToAscii(payload1);
|
|
@@ -21,23 +29,20 @@ export function verifySignature(signature, message, signingAddress, signatureKey
|
|
|
21
29
|
const cosePublicKey = coseSigKey.get(-2);
|
|
22
30
|
const sigKey = CSL.PublicKey.from_bytes(cosePublicKey);
|
|
23
31
|
const publicKeyHash = sigKey.hash();
|
|
24
|
-
const address_matches = addressBytes.toString(
|
|
32
|
+
const address_matches = addressBytes.toString('hex').slice(2) === publicKeyHash.to_hex();
|
|
25
33
|
const sig = CSL.Ed25519Signature.from_bytes(signatureBytes);
|
|
26
34
|
const validates = sigKey.verify(coseSign1.signed_data().to_bytes(), sig);
|
|
27
35
|
const message_matches = signaturePayloadAscii === message;
|
|
28
36
|
const isValid = validates && message_matches && address_matches;
|
|
29
37
|
const sigMeta = chunkString(sig.to_hex(), 64);
|
|
30
38
|
if (!isValid) {
|
|
31
|
-
console.log(
|
|
39
|
+
console.log('Failed to validate signature!');
|
|
32
40
|
console.log(isValid, validates, message_matches, address_matches);
|
|
33
41
|
}
|
|
34
42
|
return { isValid, sigMeta, pubKeyHex: sigKey.to_hex() };
|
|
35
43
|
}
|
|
36
44
|
catch (error) {
|
|
37
|
-
console.error(`Error during signature validation:`, error);
|
|
45
|
+
console.error(`Error during signature validation:`, String(error));
|
|
38
46
|
return { isValid: false, sigMeta: [], pubKeyHex: '' };
|
|
39
47
|
}
|
|
40
48
|
}
|
|
41
|
-
/**
|
|
42
|
-
* End Signature Validation Stuff
|
|
43
|
-
*/
|