@lerna-labs/hydra-sdk 1.0.0-beta.8 → 2.0.0-beta.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.
- 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/hydra-http-client.d.ts +36 -0
- package/dist/hydra/hydra-http-client.js +66 -0
- package/dist/hydra/hydra-monitor.d.ts +88 -0
- package/dist/hydra/hydra-monitor.js +270 -0
- package/dist/hydra/hydra-websocket.d.ts +46 -0
- package/dist/hydra/hydra-websocket.js +181 -0
- package/dist/hydra/messages.d.ts +14 -0
- package/dist/hydra/messages.js +1 -0
- package/dist/hydra/types.d.ts +486 -0
- package/dist/hydra/types.js +2 -0
- package/dist/hydra/utxo-conversion.d.ts +10 -0
- package/dist/hydra/utxo-conversion.js +111 -0
- package/dist/hydra/utxo.d.ts +25 -5
- package/dist/hydra/utxo.js +37 -31
- package/dist/index.d.ts +15 -7
- package/dist/index.js +11 -7
- package/dist/ipfs/ipfs.d.ts +22 -0
- package/dist/ipfs/ipfs.js +90 -0
- package/dist/mesh/get-admin.d.ts +13 -1
- package/dist/mesh/get-admin.js +37 -7
- package/dist/mesh/native-script.d.ts +30 -5
- package/dist/mesh/native-script.js +38 -10
- package/dist/test.js +3 -3
- 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 +28 -5
- package/dist/utils/verify-signature.js +39 -18
- package/dist/wrangler.d.ts +179 -0
- package/dist/wrangler.js +452 -0
- package/package.json +25 -6
- package/dist/mesh/wrangler.d.ts +0 -29
- package/dist/mesh/wrangler.js +0 -277
package/dist/hydra/utxo.js
CHANGED
|
@@ -1,52 +1,58 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { requireEnv } from '../config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch the full UTxO set from the Hydra head snapshot.
|
|
4
|
+
*
|
|
5
|
+
* Reads `HYDRA_API_URL` from the environment.
|
|
6
|
+
*
|
|
7
|
+
* @param options - Query options. Set `includeDatums: true` to include datum/script fields.
|
|
8
|
+
* @returns All UTxOs currently held in the Hydra head.
|
|
9
|
+
*/
|
|
10
|
+
export async function getUtxoSet(options) {
|
|
11
|
+
const baseUrl = requireEnv('HYDRA_API_URL');
|
|
7
12
|
const url = `${baseUrl}/snapshot/utxo`;
|
|
8
13
|
try {
|
|
9
|
-
const response = await
|
|
10
|
-
|
|
14
|
+
const response = await fetch(url);
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`HTTP ${response.status} fetching ${url}`);
|
|
17
|
+
}
|
|
18
|
+
const data = await response.json();
|
|
11
19
|
const UtxoSet = [];
|
|
12
20
|
for (const [txKey, utxo] of Object.entries(data)) {
|
|
13
|
-
const [tx_hash, index_str] = txKey.split(
|
|
21
|
+
const [tx_hash, index_str] = txKey.split('#');
|
|
14
22
|
const output_index = parseInt(index_str, 10);
|
|
15
23
|
const amount = Object.entries(utxo.value).map(([unit, quantity]) => ({
|
|
16
24
|
unit,
|
|
17
25
|
quantity,
|
|
18
26
|
}));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const parsed = { tx_hash, output_index, address: utxo.address, amount };
|
|
28
|
+
if (options?.includeDatums) {
|
|
29
|
+
parsed.datum = utxo.datum ?? null;
|
|
30
|
+
parsed.datumHash = utxo.datumHash ?? null;
|
|
31
|
+
parsed.inlineDatum = utxo.inlineDatum ?? null;
|
|
32
|
+
parsed.referenceScript = utxo.referenceScript ?? null;
|
|
33
|
+
}
|
|
34
|
+
UtxoSet.push(parsed);
|
|
25
35
|
}
|
|
26
36
|
return UtxoSet;
|
|
27
37
|
}
|
|
28
38
|
catch (error) {
|
|
29
|
-
console.error(
|
|
39
|
+
console.error('Error fetching the Hydra Ledger?', error);
|
|
30
40
|
throw error;
|
|
31
41
|
}
|
|
32
42
|
}
|
|
33
43
|
/**
|
|
34
|
-
*
|
|
44
|
+
* Fetch UTxOs belonging to a specific address from the Hydra head snapshot.
|
|
45
|
+
*
|
|
46
|
+
* @param address - Bech32 address to filter by.
|
|
47
|
+
* @returns UTxOs matching the given address.
|
|
35
48
|
*/
|
|
36
|
-
export async function queryUtxoByAddress(address) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (utxo.address === address) {
|
|
43
|
-
result.push(utxo);
|
|
44
|
-
}
|
|
49
|
+
export async function queryUtxoByAddress(address, options) {
|
|
50
|
+
const result = [];
|
|
51
|
+
const data = await getUtxoSet(options);
|
|
52
|
+
for (const utxo of data) {
|
|
53
|
+
if (utxo.address === address) {
|
|
54
|
+
result.push(utxo);
|
|
45
55
|
}
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
console.error('Failed to fetch or parse UTxO snapshot:', error.message);
|
|
50
|
-
throw error;
|
|
51
56
|
}
|
|
57
|
+
return result;
|
|
52
58
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
1
|
+
export type { DiskCache, DiskCacheConfig } from './cache/disk-cache.js';
|
|
2
|
+
export { createDiskCache } from './cache/disk-cache.js';
|
|
3
|
+
export { optionalEnv, requireEnv } from './config.js';
|
|
4
|
+
export { HydraMonitor } from './hydra/hydra-monitor.js';
|
|
5
|
+
export type { ConfirmedSnapshot, HeadStatus, HydraHeadInfo, HydraMessage, HydraMonitorOptions, HydraSnapshot, HydraStatus, HydraTransaction, HydraWsMessage, hydraStatus, hydraTransaction, ServerOutput, TimestampedEvent, } from './hydra/messages.js';
|
|
6
|
+
export type { ParsedUtxo, UtxoQueryOptions } from './hydra/utxo.js';
|
|
7
|
+
export { getUtxoSet, queryUtxoByAddress } from './hydra/utxo.js';
|
|
8
|
+
export type { IpfsClient, IpfsConfig, PinResult } from './ipfs/ipfs.js';
|
|
9
|
+
export { createIpfsClient } from './ipfs/ipfs.js';
|
|
10
|
+
export { getAdmin } from './mesh/get-admin.js';
|
|
11
|
+
export { createMultisigAddress, createNativeScript } from './mesh/native-script.js';
|
|
12
|
+
export { submitTx } from './tx3/submit-tx.js';
|
|
13
|
+
export { chunkString } from './utils/chunk-string.js';
|
|
14
|
+
export { BECH32_DECODE_LIMIT, bufferToAscii, bufferToHex, decodeBech32Address, verifySignature, } from './utils/verify-signature.js';
|
|
15
|
+
export { CommitArgs, UTxORef, Wrangler } from './wrangler.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
1
|
+
export { createDiskCache } from './cache/disk-cache.js';
|
|
2
|
+
export { optionalEnv, requireEnv } from './config.js';
|
|
3
|
+
export { HydraMonitor } from './hydra/hydra-monitor.js';
|
|
4
|
+
export { getUtxoSet, queryUtxoByAddress } from './hydra/utxo.js';
|
|
5
|
+
export { createIpfsClient } from './ipfs/ipfs.js';
|
|
6
|
+
export { getAdmin } from './mesh/get-admin.js';
|
|
7
|
+
export { createMultisigAddress, createNativeScript } from './mesh/native-script.js';
|
|
8
|
+
export { submitTx } from './tx3/submit-tx.js';
|
|
9
|
+
export { chunkString } from './utils/chunk-string.js';
|
|
10
|
+
export { BECH32_DECODE_LIMIT, bufferToAscii, bufferToHex, decodeBech32Address, verifySignature, } from './utils/verify-signature.js';
|
|
11
|
+
export { Wrangler } from './wrangler.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface IpfsConfig {
|
|
2
|
+
/** Base URL for the Kubo HTTP API (e.g. "http://localhost:5001"). */
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
}
|
|
5
|
+
export interface PinResult {
|
|
6
|
+
/** The content-identifier returned by the IPFS node. */
|
|
7
|
+
cid: string;
|
|
8
|
+
/** Size in bytes as reported by the IPFS node. */
|
|
9
|
+
size: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create an IPFS client bound to the given Kubo API endpoint.
|
|
13
|
+
*
|
|
14
|
+
* All operations go through the Kubo HTTP RPC, so the only requirement
|
|
15
|
+
* is a reachable Kubo node — no additional IPFS libraries are needed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createIpfsClient(config: IpfsConfig): {
|
|
18
|
+
pinJson: (filename: string, payload: unknown) => Promise<PinResult>;
|
|
19
|
+
pinDirectory: (dirPath: string) => Promise<PinResult>;
|
|
20
|
+
fetchJson: <T = unknown>(cid: string) => Promise<T>;
|
|
21
|
+
};
|
|
22
|
+
export type IpfsClient = ReturnType<typeof createIpfsClient>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Create an IPFS client bound to the given Kubo API endpoint.
|
|
5
|
+
*
|
|
6
|
+
* All operations go through the Kubo HTTP RPC, so the only requirement
|
|
7
|
+
* is a reachable Kubo node — no additional IPFS libraries are needed.
|
|
8
|
+
*/
|
|
9
|
+
export function createIpfsClient(config) {
|
|
10
|
+
const { apiUrl } = config;
|
|
11
|
+
/**
|
|
12
|
+
* Pin a single JSON-serialisable payload and return its CID.
|
|
13
|
+
*
|
|
14
|
+
* @param filename - Filename used inside the IPFS object.
|
|
15
|
+
* @param payload - Any JSON-serialisable value.
|
|
16
|
+
*/
|
|
17
|
+
async function pinJson(filename, payload) {
|
|
18
|
+
const body = JSON.stringify(payload, null, 2);
|
|
19
|
+
const form = new FormData();
|
|
20
|
+
form.append('file', new Blob([body], { type: 'application/json' }), filename);
|
|
21
|
+
const res = await fetch(`${apiUrl}/api/v0/add?pin=true`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body: form,
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
throw new Error(`IPFS pin failed: ${res.status} ${await res.text()}`);
|
|
27
|
+
}
|
|
28
|
+
const json = (await res.json());
|
|
29
|
+
return { cid: json.Hash, size: Number.parseInt(json.Size, 10) };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pin every file in a directory and wrap them in an IPFS directory object.
|
|
33
|
+
*
|
|
34
|
+
* @param dirPath - Absolute path to a local directory.
|
|
35
|
+
* @returns The CID of the wrapping IPFS directory.
|
|
36
|
+
*/
|
|
37
|
+
async function pinDirectory(dirPath) {
|
|
38
|
+
const form = new FormData();
|
|
39
|
+
const collectFiles = async (rel) => {
|
|
40
|
+
const out = [];
|
|
41
|
+
const dirents = await fs.readdir(path.join(dirPath, rel), { withFileTypes: true });
|
|
42
|
+
for (const dirent of dirents) {
|
|
43
|
+
const childRel = path.join(rel, dirent.name);
|
|
44
|
+
if (dirent.isDirectory()) {
|
|
45
|
+
out.push(...(await collectFiles(childRel)));
|
|
46
|
+
}
|
|
47
|
+
else if (dirent.isFile()) {
|
|
48
|
+
out.push(childRel);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
};
|
|
53
|
+
for (const relPath of await collectFiles('')) {
|
|
54
|
+
const buf = await fs.readFile(path.join(dirPath, relPath));
|
|
55
|
+
const content = new Uint8Array(buf).slice().buffer;
|
|
56
|
+
// Kubo reconstructs the directory tree from entry names — always
|
|
57
|
+
// use POSIX separators so Windows callers get the same CID.
|
|
58
|
+
const entryName = relPath.split(path.sep).join('/');
|
|
59
|
+
form.append('file', new Blob([content]), entryName);
|
|
60
|
+
}
|
|
61
|
+
const res = await fetch(`${apiUrl}/api/v0/add?pin=true&wrap-with-directory=true&recursive=true`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: form,
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
throw new Error(`IPFS directory pin failed: ${res.status} ${await res.text()}`);
|
|
67
|
+
}
|
|
68
|
+
// Kubo returns one JSON object per line (ndjson). The last line is the
|
|
69
|
+
// wrapping directory entry.
|
|
70
|
+
const text = await res.text();
|
|
71
|
+
const lines = text.trim().split('\n');
|
|
72
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
73
|
+
return { cid: last.Hash, size: Number.parseInt(last.Size, 10) };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fetch a JSON payload from IPFS by CID.
|
|
77
|
+
*
|
|
78
|
+
* @param cid - The content identifier to retrieve.
|
|
79
|
+
*/
|
|
80
|
+
async function fetchJson(cid) {
|
|
81
|
+
const res = await fetch(`${apiUrl}/api/v0/cat?arg=${cid}`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
throw new Error(`IPFS fetch failed: ${res.status} ${await res.text()}`);
|
|
86
|
+
}
|
|
87
|
+
return (await res.json());
|
|
88
|
+
}
|
|
89
|
+
return { pinJson, pinDirectory, fetchJson };
|
|
90
|
+
}
|
package/dist/mesh/get-admin.d.ts
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
import { MeshWallet } from '@meshsdk/core';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Create and initialize a MeshWallet for the Hydra head admin.
|
|
4
|
+
*
|
|
5
|
+
* Reads the Cardano signing key from `HYDRA_ADMIN_KEY_FILE` (preferred)
|
|
6
|
+
* or falls back to `HYDRA_ADMIN_CARDANO_PK`. Network is selected via `HYDRA_NETWORK`.
|
|
7
|
+
*
|
|
8
|
+
* @param blockfrostProjectId - Optional Blockfrost project ID. When provided, the wallet
|
|
9
|
+
* is configured with a Blockfrost fetcher and submitter for L1 operations (e.g. preparing
|
|
10
|
+
* the Hydra head, querying UTxOs, submitting commit transactions).
|
|
11
|
+
* @returns An initialized MeshWallet ready for signing transactions.
|
|
12
|
+
* @throws If no signing key is available or the wallet fails to initialize.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAdmin(blockfrostProjectId?: string): Promise<MeshWallet>;
|
package/dist/mesh/get-admin.js
CHANGED
|
@@ -1,23 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { BlockfrostProvider, MeshWallet } from '@meshsdk/core';
|
|
3
|
+
import { optionalEnv } from '../config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create and initialize a MeshWallet for the Hydra head admin.
|
|
6
|
+
*
|
|
7
|
+
* Reads the Cardano signing key from `HYDRA_ADMIN_KEY_FILE` (preferred)
|
|
8
|
+
* or falls back to `HYDRA_ADMIN_CARDANO_PK`. Network is selected via `HYDRA_NETWORK`.
|
|
9
|
+
*
|
|
10
|
+
* @param blockfrostProjectId - Optional Blockfrost project ID. When provided, the wallet
|
|
11
|
+
* is configured with a Blockfrost fetcher and submitter for L1 operations (e.g. preparing
|
|
12
|
+
* the Hydra head, querying UTxOs, submitting commit transactions).
|
|
13
|
+
* @returns An initialized MeshWallet ready for signing transactions.
|
|
14
|
+
* @throws If no signing key is available or the wallet fails to initialize.
|
|
15
|
+
*/
|
|
16
|
+
export async function getAdmin(blockfrostProjectId) {
|
|
17
|
+
let keyCborHex = null;
|
|
18
|
+
// Preferred: read the instance's cardano.sk file directly (no secrets in .env)
|
|
19
|
+
const keyFile = process.env.HYDRA_ADMIN_KEY_FILE;
|
|
20
|
+
if (keyFile) {
|
|
21
|
+
const content = JSON.parse(readFileSync(keyFile, 'utf-8'));
|
|
22
|
+
keyCborHex = content.cborHex ?? content.key?.cborHex ?? null;
|
|
23
|
+
}
|
|
24
|
+
// Fallback: cborHex passed via env var (backward compatible)
|
|
25
|
+
if (!keyCborHex) {
|
|
26
|
+
keyCborHex = process.env.HYDRA_ADMIN_CARDANO_PK || null;
|
|
27
|
+
}
|
|
4
28
|
if (!keyCborHex) {
|
|
5
|
-
throw new Error('
|
|
29
|
+
throw new Error('Cardano signing key not found. Set HYDRA_ADMIN_KEY_FILE to the instance cardano.sk path, or HYDRA_ADMIN_CARDANO_PK to its cborHex.');
|
|
6
30
|
}
|
|
7
|
-
let networkId = parseInt(
|
|
31
|
+
let networkId = parseInt(optionalEnv('HYDRA_NETWORK', '0'), 10);
|
|
8
32
|
if (networkId < 0) {
|
|
9
33
|
networkId = 0;
|
|
10
34
|
}
|
|
11
35
|
else if (networkId > 1) {
|
|
12
36
|
networkId = 1;
|
|
13
37
|
}
|
|
14
|
-
const
|
|
38
|
+
const walletOptions = {
|
|
15
39
|
networkId: networkId,
|
|
16
40
|
key: {
|
|
17
41
|
type: 'cli',
|
|
18
42
|
payment: keyCborHex,
|
|
19
43
|
},
|
|
20
|
-
}
|
|
44
|
+
};
|
|
45
|
+
if (blockfrostProjectId) {
|
|
46
|
+
const blockfrost = new BlockfrostProvider(blockfrostProjectId);
|
|
47
|
+
walletOptions.fetcher = blockfrost;
|
|
48
|
+
walletOptions.submitter = blockfrost;
|
|
49
|
+
}
|
|
50
|
+
const wallet = new MeshWallet(walletOptions);
|
|
21
51
|
await wallet.init();
|
|
22
52
|
if (!wallet.addresses.enterpriseAddressBech32) {
|
|
23
53
|
throw new Error('Wallet failed to initialize!');
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Create a multisig address
|
|
3
|
-
*
|
|
4
|
-
* @param
|
|
5
|
-
* @param
|
|
6
|
-
* @param
|
|
2
|
+
* Create a multisig script address from two Cardano addresses.
|
|
3
|
+
*
|
|
4
|
+
* @param address1 - First address (bech32) to include in the native script.
|
|
5
|
+
* @param address2 - Second address (bech32) to include in the native script.
|
|
6
|
+
* @param networkId - `0` for testnet, `1` for mainnet.
|
|
7
|
+
* @param scriptType - `"any"` requires one signature, `"all"` requires both.
|
|
8
|
+
* @returns The script address, serialized CBOR, and script hash.
|
|
7
9
|
*/
|
|
8
10
|
export declare function createMultisigAddress(address1: string, address2: string, networkId?: number, scriptType?: 'any' | 'all'): {
|
|
9
11
|
address: string;
|
|
10
12
|
scriptCbor?: string;
|
|
11
13
|
scriptHash?: string;
|
|
12
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Create a native script policy for minting tokens.
|
|
17
|
+
*
|
|
18
|
+
* Without options, produces a bare `sig(keyHash)` script suitable for
|
|
19
|
+
* in-head voter tokens that must remain burnable by the admin.
|
|
20
|
+
*
|
|
21
|
+
* When `invalidHereafter` is provided, produces a compound time-bound
|
|
22
|
+
* script: `all: [sig(keyHash), before(slot)]` — each ballot gets its
|
|
23
|
+
* own time-bound policy.
|
|
24
|
+
*
|
|
25
|
+
* @param address - Address (bech32) whose key hash is the required signer.
|
|
26
|
+
* @param opts.invalidHereafter - Slot after which minting is no longer possible.
|
|
27
|
+
* @param opts.networkId - `0` for testnet (default), `1` for mainnet.
|
|
28
|
+
* @returns The script address, serialized CBOR, and script hash.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createNativeScript(address: string, opts?: {
|
|
31
|
+
invalidHereafter?: number;
|
|
32
|
+
networkId?: number;
|
|
33
|
+
}): {
|
|
34
|
+
address: string;
|
|
35
|
+
scriptCbor?: string;
|
|
36
|
+
scriptHash?: string;
|
|
37
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deserializeAddress, resolveScriptHash, serializeNativeScript } from '@meshsdk/core';
|
|
2
2
|
/**
|
|
3
|
-
* Create a multisig address
|
|
4
|
-
*
|
|
5
|
-
* @param
|
|
6
|
-
* @param
|
|
7
|
-
* @param
|
|
3
|
+
* Create a multisig script address from two Cardano addresses.
|
|
4
|
+
*
|
|
5
|
+
* @param address1 - First address (bech32) to include in the native script.
|
|
6
|
+
* @param address2 - Second address (bech32) to include in the native script.
|
|
7
|
+
* @param networkId - `0` for testnet, `1` for mainnet.
|
|
8
|
+
* @param scriptType - `"any"` requires one signature, `"all"` requires both.
|
|
9
|
+
* @returns The script address, serialized CBOR, and script hash.
|
|
8
10
|
*/
|
|
9
11
|
export function createMultisigAddress(address1, address2, networkId = 0, scriptType = 'any') {
|
|
10
12
|
const keyHash1 = deserializeAddress(address1).pubKeyHash;
|
|
@@ -23,9 +25,35 @@ export function createMultisigAddress(address1, address2, networkId = 0, scriptT
|
|
|
23
25
|
],
|
|
24
26
|
};
|
|
25
27
|
const { address, scriptCbor } = serializeNativeScript(script, undefined, networkId);
|
|
26
|
-
|
|
27
|
-
if (scriptCbor != null) {
|
|
28
|
-
scriptHash = resolveScriptHash(scriptCbor);
|
|
29
|
-
}
|
|
28
|
+
const scriptHash = scriptCbor != null ? resolveScriptHash(scriptCbor) : undefined;
|
|
30
29
|
return { address, scriptCbor, scriptHash };
|
|
31
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a native script policy for minting tokens.
|
|
33
|
+
*
|
|
34
|
+
* Without options, produces a bare `sig(keyHash)` script suitable for
|
|
35
|
+
* in-head voter tokens that must remain burnable by the admin.
|
|
36
|
+
*
|
|
37
|
+
* When `invalidHereafter` is provided, produces a compound time-bound
|
|
38
|
+
* script: `all: [sig(keyHash), before(slot)]` — each ballot gets its
|
|
39
|
+
* own time-bound policy.
|
|
40
|
+
*
|
|
41
|
+
* @param address - Address (bech32) whose key hash is the required signer.
|
|
42
|
+
* @param opts.invalidHereafter - Slot after which minting is no longer possible.
|
|
43
|
+
* @param opts.networkId - `0` for testnet (default), `1` for mainnet.
|
|
44
|
+
* @returns The script address, serialized CBOR, and script hash.
|
|
45
|
+
*/
|
|
46
|
+
export function createNativeScript(address, opts) {
|
|
47
|
+
const networkId = opts?.networkId ?? 0;
|
|
48
|
+
const keyHash = deserializeAddress(address).pubKeyHash;
|
|
49
|
+
const sigScript = { type: 'sig', keyHash };
|
|
50
|
+
const script = opts?.invalidHereafter != null
|
|
51
|
+
? {
|
|
52
|
+
type: 'all',
|
|
53
|
+
scripts: [sigScript, { type: 'before', slot: opts.invalidHereafter.toString() }],
|
|
54
|
+
}
|
|
55
|
+
: sigScript;
|
|
56
|
+
const { address: scriptAddress, scriptCbor } = serializeNativeScript(script, undefined, networkId);
|
|
57
|
+
const scriptHash = scriptCbor != null ? resolveScriptHash(scriptCbor) : undefined;
|
|
58
|
+
return { address: scriptAddress, scriptCbor, scriptHash };
|
|
59
|
+
}
|
package/dist/test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as dotenv from 'dotenv';
|
|
2
2
|
dotenv.config({ path: '.local.env' });
|
|
3
3
|
import { BlockfrostProvider, MeshTxBuilder, MeshWallet } from '@meshsdk/core';
|
|
4
|
-
import { Wrangler } from './
|
|
4
|
+
import { Wrangler } from './wrangler.js';
|
|
5
5
|
(async () => {
|
|
6
6
|
const admin_wallet = new MeshWallet({
|
|
7
7
|
networkId: parseInt(process.env.HYDRA_NETWORK_ID || '0', 10),
|
|
@@ -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,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
/** Convert a buffer-like value to a hex string. */
|
|
3
|
+
export declare const bufferToHex: (buffer: Uint8Array | string) => string;
|
|
4
|
+
/** Convert a buffer-like value to an ASCII string. */
|
|
5
|
+
export declare const bufferToAscii: (buffer: Uint8Array | string) => string;
|
|
6
|
+
/**
|
|
7
|
+
* Character limit used for bech32 decoding. The library defaults to 90, which truncates
|
|
8
|
+
* Cardano base addresses (payment + staking) that are commonly 103+ characters. We raise
|
|
9
|
+
* the ceiling well above any realistic Cardano encoding (addr, stake, drep, pool, cc_*).
|
|
10
|
+
*/
|
|
11
|
+
export declare const BECH32_DECODE_LIMIT = 1023;
|
|
12
|
+
/**
|
|
13
|
+
* Decode a Cardano bech32 string (addr, stake, pool, drep, cc_*) with a character
|
|
14
|
+
* limit high enough to accommodate base addresses that include a staking component.
|
|
15
|
+
*/
|
|
16
|
+
export declare function decodeBech32Address(address: string): {
|
|
17
|
+
prefix: string;
|
|
18
|
+
addressBytes: Buffer;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Verify a CIP-30 COSE_Sign1 signature against an expected message and address.
|
|
22
|
+
*
|
|
23
|
+
* @param signature - Hex-encoded COSE_Sign1 signature bytes.
|
|
24
|
+
* @param message - The original plaintext message that was signed.
|
|
25
|
+
* @param signingAddress - Bech32 address of the expected signer.
|
|
26
|
+
* @param signatureKey - Hex-encoded COSE key containing the public key.
|
|
27
|
+
* @returns Validation result with `isValid`, chunked signature metadata, and public key hex.
|
|
28
|
+
*/
|
|
3
29
|
export declare function verifySignature(signature: string, message: string, signingAddress: string, signatureKey: string): {
|
|
4
30
|
isValid: boolean;
|
|
5
31
|
sigMeta: string[];
|
|
6
32
|
pubKeyHex: string;
|
|
7
33
|
};
|
|
8
|
-
/**
|
|
9
|
-
* End Signature Validation Stuff
|
|
10
|
-
*/
|
|
@@ -1,43 +1,64 @@
|
|
|
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
|
+
* Character limit used for bech32 decoding. The library defaults to 90, which truncates
|
|
13
|
+
* Cardano base addresses (payment + staking) that are commonly 103+ characters. We raise
|
|
14
|
+
* the ceiling well above any realistic Cardano encoding (addr, stake, drep, pool, cc_*).
|
|
15
|
+
*/
|
|
16
|
+
export const BECH32_DECODE_LIMIT = 1023;
|
|
17
|
+
/**
|
|
18
|
+
* Decode a Cardano bech32 string (addr, stake, pool, drep, cc_*) with a character
|
|
19
|
+
* limit high enough to accommodate base addresses that include a staking component.
|
|
20
|
+
*/
|
|
21
|
+
export function decodeBech32Address(address) {
|
|
22
|
+
const { words, prefix } = bech32.decode(address, BECH32_DECODE_LIMIT);
|
|
23
|
+
return { prefix, addressBytes: Buffer.from(bech32.fromWords(words)) };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Verify a CIP-30 COSE_Sign1 signature against an expected message and address.
|
|
27
|
+
*
|
|
28
|
+
* @param signature - Hex-encoded COSE_Sign1 signature bytes.
|
|
29
|
+
* @param message - The original plaintext message that was signed.
|
|
30
|
+
* @param signingAddress - Bech32 address of the expected signer.
|
|
31
|
+
* @param signatureKey - Hex-encoded COSE key containing the public key.
|
|
32
|
+
* @returns Validation result with `isValid`, chunked signature metadata, and public key hex.
|
|
3
33
|
*/
|
|
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
34
|
export function verifySignature(signature, message, signingAddress, signatureKey) {
|
|
13
35
|
try {
|
|
14
|
-
const coseSign1 = COSESign1.from_bytes(Buffer.from(signature,
|
|
36
|
+
const coseSign1 = COSESign1.from_bytes(Buffer.from(signature, 'hex'));
|
|
15
37
|
const signatureBytes = coseSign1.signature();
|
|
16
38
|
const [, , , payload1] = cbor.decode(bufferToHex(coseSign1.signed_data().to_bytes()));
|
|
17
39
|
const signaturePayloadAscii = bufferToAscii(payload1);
|
|
18
|
-
const {
|
|
19
|
-
const addressBytes = Buffer.from(bech32.fromWords(words));
|
|
40
|
+
const { prefix, addressBytes } = decodeBech32Address(signingAddress);
|
|
20
41
|
const coseSigKey = cbor.decode(signatureKey);
|
|
21
42
|
const cosePublicKey = coseSigKey.get(-2);
|
|
22
43
|
const sigKey = CSL.PublicKey.from_bytes(cosePublicKey);
|
|
23
44
|
const publicKeyHash = sigKey.hash();
|
|
24
|
-
|
|
45
|
+
// pool1... bech32 encodes the raw 28-byte key hash with no header byte.
|
|
46
|
+
// All other address types (addr, drep, stake) have a 1-byte header prefix.
|
|
47
|
+
const addressHex = addressBytes.toString('hex');
|
|
48
|
+
const address_matches = prefix === 'pool' ? addressHex === publicKeyHash.to_hex() : addressHex.slice(2) === publicKeyHash.to_hex();
|
|
25
49
|
const sig = CSL.Ed25519Signature.from_bytes(signatureBytes);
|
|
26
50
|
const validates = sigKey.verify(coseSign1.signed_data().to_bytes(), sig);
|
|
27
51
|
const message_matches = signaturePayloadAscii === message;
|
|
28
52
|
const isValid = validates && message_matches && address_matches;
|
|
29
53
|
const sigMeta = chunkString(sig.to_hex(), 64);
|
|
30
54
|
if (!isValid) {
|
|
31
|
-
console.log(
|
|
55
|
+
console.log('Failed to validate signature!');
|
|
32
56
|
console.log(isValid, validates, message_matches, address_matches);
|
|
33
57
|
}
|
|
34
58
|
return { isValid, sigMeta, pubKeyHex: sigKey.to_hex() };
|
|
35
59
|
}
|
|
36
60
|
catch (error) {
|
|
37
|
-
console.error(`Error during signature validation:`, error);
|
|
61
|
+
console.error(`Error during signature validation:`, String(error));
|
|
38
62
|
return { isValid: false, sigMeta: [], pubKeyHex: '' };
|
|
39
63
|
}
|
|
40
64
|
}
|
|
41
|
-
/**
|
|
42
|
-
* End Signature Validation Stuff
|
|
43
|
-
*/
|