@lerna-labs/hydra-sdk 1.0.0-beta.6

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.
@@ -0,0 +1,14 @@
1
+ interface ParsedUtxo {
2
+ tx_hash: string;
3
+ output_index: number;
4
+ address: string;
5
+ amount: {
6
+ unit: string;
7
+ quantity: string;
8
+ }[];
9
+ }
10
+ /**
11
+ * Fetches the full UTxO snapshot and filters by address.
12
+ */
13
+ export declare function queryUtxoByAddress(address: string): Promise<ParsedUtxo[]>;
14
+ export {};
@@ -0,0 +1,40 @@
1
+ import axios from 'axios';
2
+ /**
3
+ * Fetches the full UTxO snapshot and filters by address.
4
+ */
5
+ export async function queryUtxoByAddress(address) {
6
+ console.log(`Querying UTxO by Address: ${address}`);
7
+ const baseUrl = process.env.HYDRA_API_URL;
8
+ console.log(`Base Url: ${baseUrl}`);
9
+ if (!baseUrl) {
10
+ throw new Error('HYDRA_API_URL is not defined in the environment variables.');
11
+ }
12
+ const url = `${baseUrl}/snapshot/utxo`;
13
+ try {
14
+ const response = await axios.get(url);
15
+ const data = response.data;
16
+ console.log(`Did get data?`, Object.entries(data).length);
17
+ const result = [];
18
+ for (const [txKey, utxo] of Object.entries(data)) {
19
+ if (utxo.address === address) {
20
+ const [tx_hash, indexStr] = txKey.split('#');
21
+ const output_index = parseInt(indexStr, 10);
22
+ const amount = Object.entries(utxo.value).map(([unit, quantity]) => ({
23
+ unit,
24
+ quantity,
25
+ }));
26
+ result.push({
27
+ tx_hash,
28
+ output_index,
29
+ address: utxo.address,
30
+ amount,
31
+ });
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+ catch (error) {
37
+ console.error('Failed to fetch or parse UTxO snapshot:', error.message);
38
+ throw error;
39
+ }
40
+ }
@@ -0,0 +1,7 @@
1
+ export * from './mesh/get-admin';
2
+ export * from './mesh/native-script';
3
+ export * from './mesh/wrangler';
4
+ export * from './hydra/utxo';
5
+ export * from './tx3/submit-tx';
6
+ export * from './utils/verify-signature';
7
+ export * from './utils/chunk-string';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from './mesh/get-admin';
2
+ export * from './mesh/native-script';
3
+ export * from './mesh/wrangler';
4
+ export * from './hydra/utxo';
5
+ export * from './tx3/submit-tx';
6
+ export * from './utils/verify-signature';
7
+ export * from './utils/chunk-string';
@@ -0,0 +1,2 @@
1
+ import { MeshWallet } from '@meshsdk/core';
2
+ export declare function getAdmin(): Promise<MeshWallet>;
@@ -0,0 +1,26 @@
1
+ import { MeshWallet, } from '@meshsdk/core';
2
+ export async function getAdmin() {
3
+ const keyCborHex = process.env.HYDRA_ADMIN_CARDANO_PK || null;
4
+ if (!keyCborHex) {
5
+ throw new Error('Admin signing key is not defined!');
6
+ }
7
+ let networkId = parseInt(process.env.HYDRA_NETWORK || '0', 10);
8
+ if (networkId < 0) {
9
+ networkId = 0;
10
+ }
11
+ else if (networkId > 1) {
12
+ networkId = 1;
13
+ }
14
+ const wallet = new MeshWallet({
15
+ networkId: networkId,
16
+ key: {
17
+ type: 'cli',
18
+ payment: keyCborHex,
19
+ },
20
+ });
21
+ await wallet.init();
22
+ if (!wallet.addresses.enterpriseAddressBech32) {
23
+ throw new Error('Wallet failed to initialize!');
24
+ }
25
+ return wallet;
26
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Create a multisig address using 'any' policy (AND logic)
3
+ * @param address1 The first address to include in the script (staking or payment)
4
+ * @param address2 The second address to include in the script (staking or payment)
5
+ * @param networkId 0 = testnet, 1 = mainnet
6
+ * @param scriptType
7
+ */
8
+ export declare function createMultisigAddress(address1: string, address2: string, networkId?: number, scriptType?: 'any' | 'all'): {
9
+ address: string;
10
+ scriptCbor?: string;
11
+ scriptHash?: string;
12
+ };
@@ -0,0 +1,31 @@
1
+ import { serializeNativeScript, resolveScriptHash, deserializeAddress, } from '@meshsdk/core';
2
+ /**
3
+ * Create a multisig address using 'any' policy (AND logic)
4
+ * @param address1 The first address to include in the script (staking or payment)
5
+ * @param address2 The second address to include in the script (staking or payment)
6
+ * @param networkId 0 = testnet, 1 = mainnet
7
+ * @param scriptType
8
+ */
9
+ export function createMultisigAddress(address1, address2, networkId = 0, scriptType = 'any') {
10
+ const keyHash1 = deserializeAddress(address1).pubKeyHash;
11
+ const keyHash2 = deserializeAddress(address2).pubKeyHash;
12
+ const script = {
13
+ type: scriptType,
14
+ scripts: [
15
+ {
16
+ type: 'sig',
17
+ keyHash: keyHash1,
18
+ },
19
+ {
20
+ type: 'sig',
21
+ keyHash: keyHash2,
22
+ },
23
+ ],
24
+ };
25
+ const { address, scriptCbor } = serializeNativeScript(script, undefined, networkId);
26
+ let scriptHash;
27
+ if (scriptCbor != null) {
28
+ scriptHash = resolveScriptHash(scriptCbor);
29
+ }
30
+ return { address, scriptCbor, scriptHash };
31
+ }
@@ -0,0 +1,28 @@
1
+ import { HydraProvider } from "@meshsdk/hydra";
2
+ export interface CommitArgs {
3
+ txHash: string;
4
+ txIndex: number;
5
+ }
6
+ export declare class Wrangler {
7
+ private readonly BLOCKFROST_KEY;
8
+ private mode;
9
+ readonly provider: HydraProvider;
10
+ private instance;
11
+ private readonly blockfrost;
12
+ private readonly url;
13
+ private readonly wsUrl;
14
+ constructor(url?: string, wsUrl?: string);
15
+ private createHydraProvider;
16
+ private createHydraInstance;
17
+ connect(): Promise<void>;
18
+ startHead(txHash: string, txIndex: number): Promise<void>;
19
+ waitForHeadOpen(commitArgs: {
20
+ txHash: string;
21
+ txIndex: number;
22
+ }, timeoutMs?: number): Promise<void>;
23
+ getHeadStatus(timeoutMs?: number): Promise<string>;
24
+ shutdownHead(): Promise<void>;
25
+ private doCommit;
26
+ private handleIncoming;
27
+ private onGreetings;
28
+ }
@@ -0,0 +1,216 @@
1
+ import { HydraInstance, HydraProvider } from "@meshsdk/hydra";
2
+ import { BlockfrostProvider } from "@meshsdk/core";
3
+ const BLOCKFROST_KEY = process.env.BLOCKFROST_API_KEY;
4
+ if (!BLOCKFROST_KEY)
5
+ throw new Error("BLOCKFROST_API_KEY not set");
6
+ export class Wrangler {
7
+ BLOCKFROST_KEY;
8
+ mode;
9
+ provider;
10
+ instance;
11
+ blockfrost;
12
+ url;
13
+ wsUrl;
14
+ constructor(url, wsUrl) {
15
+ this.url = url || process.env.HYDRA_API_URL;
16
+ this.wsUrl = wsUrl || process.env.HYDRA_WS_URL;
17
+ this.BLOCKFROST_KEY = process.env.BLOCKFROST_API_KEY;
18
+ this.blockfrost = new BlockfrostProvider(this.BLOCKFROST_KEY);
19
+ this.provider = this.createHydraProvider();
20
+ this.instance = this.createHydraInstance();
21
+ }
22
+ createHydraProvider() {
23
+ return new HydraProvider({ httpUrl: this.url, history: false });
24
+ }
25
+ createHydraInstance() {
26
+ return new HydraInstance({
27
+ provider: this.provider,
28
+ fetcher: this.blockfrost,
29
+ submitter: this.provider,
30
+ });
31
+ }
32
+ async connect() {
33
+ return await this.provider.connect();
34
+ }
35
+ async startHead(txHash, txIndex) {
36
+ this.mode = "start";
37
+ this.provider.onMessage(msg => this.handleIncoming(msg, { txHash, txIndex }));
38
+ await this.provider.connect();
39
+ }
40
+ async waitForHeadOpen(commitArgs, timeoutMs = 180000) {
41
+ this.mode = "start";
42
+ return new Promise(async (resolve, reject) => {
43
+ let settled = false;
44
+ const handle = async (message) => {
45
+ try {
46
+ if (message.tag === "HeadIsOpen") {
47
+ if (settled)
48
+ return;
49
+ settled = true;
50
+ resolve();
51
+ }
52
+ else if (message.tag === "HeadIsInitializing") {
53
+ if (!commitArgs)
54
+ return;
55
+ await this.doCommit(commitArgs);
56
+ }
57
+ else if (message.tag === "Greetings") {
58
+ await this.onGreetings(message.headStatus, commitArgs);
59
+ }
60
+ }
61
+ catch (err) {
62
+ if (!settled) {
63
+ settled = true;
64
+ reject(err);
65
+ }
66
+ }
67
+ };
68
+ this.provider.onMessage(handle);
69
+ try {
70
+ await this.provider.connect();
71
+ }
72
+ catch (err) {
73
+ if (!settled) {
74
+ settled = true;
75
+ return reject(new Error("Failed to connect to Hydra provider: " + String(err)));
76
+ }
77
+ }
78
+ const timer = setTimeout(() => {
79
+ if (!settled) {
80
+ settled = true;
81
+ reject(new Error("Timeout waiting for head to open"));
82
+ }
83
+ }, timeoutMs);
84
+ const finalizer = () => clearTimeout(timer);
85
+ const origResolve = resolve;
86
+ const origReject = reject;
87
+ resolve = (v) => {
88
+ finalizer();
89
+ origResolve(v);
90
+ };
91
+ reject = (e) => {
92
+ finalizer();
93
+ origReject(e);
94
+ };
95
+ });
96
+ }
97
+ async getHeadStatus(timeoutMs = 5000) {
98
+ return new Promise(async (resolve, reject) => {
99
+ let settled = false;
100
+ const handle = (message) => {
101
+ if (settled)
102
+ return;
103
+ if (message.tag === "Greetings") {
104
+ settled = true;
105
+ resolve(message.headStatus);
106
+ }
107
+ };
108
+ this.provider.onMessage(handle);
109
+ try {
110
+ await this.provider.connect();
111
+ }
112
+ catch (err) {
113
+ if (!settled) {
114
+ settled = true;
115
+ return reject(new Error("Failed to connect to Hydra provider: " + String(err)));
116
+ }
117
+ }
118
+ const timer = setTimeout(() => {
119
+ if (!settled) {
120
+ settled = true;
121
+ reject(new Error("Timeout waiting for head to open"));
122
+ }
123
+ }, timeoutMs);
124
+ const finalizer = () => clearTimeout(timer);
125
+ const origResolve = resolve;
126
+ const origReject = reject;
127
+ resolve = (v) => {
128
+ finalizer();
129
+ origResolve(v);
130
+ };
131
+ reject = (e) => {
132
+ finalizer();
133
+ origReject(e);
134
+ };
135
+ });
136
+ }
137
+ async shutdownHead() {
138
+ this.mode = "shutdown";
139
+ this.provider.onMessage(msg => this.handleIncoming(msg));
140
+ await this.provider.connect();
141
+ }
142
+ async doCommit(commitArgs) {
143
+ try {
144
+ const rawTx = await this.instance.commitFunds(commitArgs.txHash, commitArgs.txIndex);
145
+ return await this.blockfrost.submitTx(rawTx);
146
+ }
147
+ catch (err) {
148
+ console.error(`Commit error`, err);
149
+ return false;
150
+ }
151
+ }
152
+ async handleIncoming(message, commitArgs) {
153
+ if (message.tag === "Greetings") {
154
+ await this.onGreetings(message.headStatus, commitArgs);
155
+ }
156
+ else {
157
+ switch (this.mode) {
158
+ case "start":
159
+ if (message.tag === "HeadIsInitializing") {
160
+ if (commitArgs === undefined) {
161
+ console.error("No commit arguments specified... aborting commit!");
162
+ return;
163
+ }
164
+ await this.doCommit(commitArgs);
165
+ }
166
+ if (message.tag === "HeadIsOpen") {
167
+ // Successfully started the head here... close gracefully?
168
+ }
169
+ break;
170
+ case "shutdown":
171
+ if (message.tag === "ReadyToFanout") {
172
+ await this.provider.fanout();
173
+ }
174
+ break;
175
+ }
176
+ }
177
+ }
178
+ async onGreetings(status, commitArgs) {
179
+ switch (this.mode) {
180
+ case "start":
181
+ switch (status) {
182
+ case "Idle":
183
+ console.log("Idle → init()");
184
+ await this.provider.init();
185
+ break;
186
+ case "Initializing":
187
+ console.log("Initializing -> commit()");
188
+ if (commitArgs === undefined) {
189
+ console.error("No commit arguments specified... aborting commit!");
190
+ return;
191
+ }
192
+ await this.doCommit(commitArgs);
193
+ break;
194
+ case "Open":
195
+ console.log("Open → already ready, proceeding");
196
+ break;
197
+ default:
198
+ console.log(`Greetings in start mode, ignoring status: ${status}`);
199
+ }
200
+ break;
201
+ case "shutdown":
202
+ switch (status) {
203
+ case "Open":
204
+ console.log("Shutting down: closing head…");
205
+ await this.provider.close();
206
+ break;
207
+ case "FanoutPossible":
208
+ console.log("Fanout now possible: fanning out…");
209
+ await this.provider.fanout();
210
+ break;
211
+ default:
212
+ console.log(`Greetings in shutdown mode, ignoring status: ${status}`);
213
+ }
214
+ }
215
+ }
216
+ }
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/test.js ADDED
@@ -0,0 +1,66 @@
1
+ import * as dotenv from 'dotenv';
2
+ dotenv.config({ path: '.local.env' });
3
+ import { BlockfrostProvider, MeshTxBuilder, MeshWallet } from '@meshsdk/core';
4
+ import { Wrangler } from './mesh/wrangler';
5
+ (async () => {
6
+ const admin_wallet = new MeshWallet({
7
+ networkId: parseInt(process.env.HYDRA_NETWORK_ID || '0', 10),
8
+ key: {
9
+ type: 'cli',
10
+ payment: process.env.HYDRA_ADMIN_CARDANO_PK,
11
+ },
12
+ });
13
+ await admin_wallet.init();
14
+ const admin_address = admin_wallet.addresses.enterpriseAddressBech32;
15
+ console.log(`Admin address: ${admin_address}`);
16
+ const blockfrostProvider = new BlockfrostProvider(process.env.BLOCKFROST_API_KEY);
17
+ const txHash = 'a000003f633d9b2efcc18dedefaf60623e3132c8b05a5751ac08d3bf6f505d54';
18
+ const txIndex = 3;
19
+ const utxo = await blockfrostProvider.fetchAddressUTxOs(admin_address);
20
+ if (utxo.length < 3) {
21
+ console.log(utxo);
22
+ const unsigned_tx = await new MeshTxBuilder({
23
+ fetcher: blockfrostProvider,
24
+ })
25
+ .txOut(admin_address, [
26
+ {
27
+ unit: 'lovelace',
28
+ quantity: '5000000',
29
+ },
30
+ ])
31
+ .txOut(admin_address, [
32
+ {
33
+ unit: 'lovelace',
34
+ quantity: '5000000',
35
+ },
36
+ ])
37
+ .txOut(admin_address, [
38
+ {
39
+ unit: 'lovelace',
40
+ quantity: '5000000',
41
+ },
42
+ ])
43
+ .txOut(admin_address, [
44
+ {
45
+ unit: 'lovelace',
46
+ quantity: '5000000',
47
+ },
48
+ ])
49
+ .txOut(admin_address, [
50
+ {
51
+ unit: 'lovelace',
52
+ quantity: '5000000',
53
+ },
54
+ ])
55
+ .changeAddress(admin_address)
56
+ .selectUtxosFrom(utxo)
57
+ .complete();
58
+ const signed = await admin_wallet.signTx(unsigned_tx);
59
+ await blockfrostProvider.submitTx(signed);
60
+ console.log('Just created some seed utxo... give it a minute!');
61
+ return;
62
+ }
63
+ const wrangler = new Wrangler();
64
+ // await wrangler.startHead(txHash, txIndex);
65
+ await wrangler.shutdownHead();
66
+ })();
@@ -0,0 +1 @@
1
+ export declare function submitTx(submit_endpoint: string, payload: string, id: string): Promise<Response>;
@@ -0,0 +1,20 @@
1
+ export async function submitTx(submit_endpoint, payload, id) {
2
+ return await fetch(submit_endpoint, {
3
+ method: 'POST',
4
+ headers: {
5
+ 'Content-Type': 'application/json',
6
+ },
7
+ body: JSON.stringify({
8
+ jsonrpc: '2.0',
9
+ method: 'trp.submit',
10
+ params: {
11
+ tx: {
12
+ payload,
13
+ encoding: 'hex',
14
+ version: 'v1alpha6',
15
+ },
16
+ },
17
+ id,
18
+ }),
19
+ });
20
+ }
@@ -0,0 +1 @@
1
+ export declare function chunkString(str: string, size: number): string[];
@@ -0,0 +1,7 @@
1
+ export function chunkString(str, size) {
2
+ const chunks = [];
3
+ for (let i = 0; i < str.length; i += size) {
4
+ chunks.push(str.slice(i, i + size));
5
+ }
6
+ return chunks;
7
+ }
@@ -0,0 +1,10 @@
1
+ export declare const bufferToHex: (buffer: any) => string;
2
+ export declare const bufferToAscii: (buffer: any) => string;
3
+ export declare function verifySignature(signature: string, message: string, signingAddress: string, signatureKey: string): {
4
+ isValid: boolean;
5
+ sigMeta: string[];
6
+ pubKeyHex: string;
7
+ };
8
+ /**
9
+ * End Signature Validation Stuff
10
+ */
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Signature Validation Stuff
3
+ */
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";
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
+ export function verifySignature(signature, message, signingAddress, signatureKey) {
13
+ try {
14
+ const coseSign1 = COSESign1.from_bytes(Buffer.from(signature, "hex"));
15
+ const signatureBytes = coseSign1.signature();
16
+ const [, , , payload1] = cbor.decode(bufferToHex(coseSign1.signed_data().to_bytes()));
17
+ const signaturePayloadAscii = bufferToAscii(payload1);
18
+ const { words } = bech32.decode(signingAddress);
19
+ const addressBytes = Buffer.from(bech32.fromWords(words));
20
+ const coseSigKey = cbor.decode(signatureKey);
21
+ const cosePublicKey = coseSigKey.get(-2);
22
+ const sigKey = CSL.PublicKey.from_bytes(cosePublicKey);
23
+ const publicKeyHash = sigKey.hash();
24
+ const address_matches = addressBytes.toString("hex").slice(2) === publicKeyHash.to_hex();
25
+ const sig = CSL.Ed25519Signature.from_bytes(signatureBytes);
26
+ const validates = sigKey.verify(coseSign1.signed_data().to_bytes(), sig);
27
+ const message_matches = signaturePayloadAscii === message;
28
+ const isValid = validates && message_matches && address_matches;
29
+ const sigMeta = chunkString(sig.to_hex(), 64);
30
+ if (!isValid) {
31
+ console.log("Failed to validate signature!");
32
+ console.log(isValid, validates, message_matches, address_matches);
33
+ }
34
+ return { isValid, sigMeta, pubKeyHex: sigKey.to_hex() };
35
+ }
36
+ catch (error) {
37
+ console.error(`Error during signature validation:`, error);
38
+ return { isValid: false, sigMeta: [], pubKeyHex: '' };
39
+ }
40
+ }
41
+ /**
42
+ * End Signature Validation Stuff
43
+ */
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@lerna-labs/hydra-sdk",
3
+ "version": "1.0.0-beta.6",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./*": "./dist/*"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "provenance": true,
25
+ "tag": "beta"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "dependencies": {},
31
+ "devDependencies": {},
32
+ "homepage": "https://github.com/Lerna-Labs/hydra-sdk#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/Lerna-Labs/hydra-sdk/issues"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/Lerna-Labs/hydra-sdk",
39
+ "directory": "packages/core"
40
+ }
41
+ }