@nucastio/sub-hydra
v1.0.1
Published
Hydra Subscriptions Library for Cardano
Downloads
27
Maintainers
Readme
Hydra Subscriptions Library
Prerequisites
Hydra Node
hydra-node --node-id "NODE_ID" --persistence-dir persistence-alice --cardano-signing-key credentials/alice-node.sk --hydra-signing-key credentials/alice-hydra.sk --hydra-scripts-tx-id $(curl https://raw.githubusercontent.com/cardano-scaling/hydra/master/hydra-node/networks.json | jq -r ".preprod.\"${hydra_version}\"") --ledger-protocol-parameters protocol-parameters.json --testnet-magic 1 --node-socket node.socket --api-port 4001 --listen 0.0.0.0:5001 --api-host 0.0.0.0 --deposit-period 180s --contestation-period 60sBlockfrost API Key
Subscription Node - https://github.com/Nucastio/sub-hydra-node.git
Installation
npm install @nucastio/sub-hydraHigh-Level Flow
Roles
- User
- Locks funds in a subscription script on L1
- Registers with the subscription node
- Requests/uses access proofs (signatures) to consume the service
- Can pause/cancel/withdraw
- Operator
- Runs a Hydra head
- Commits subscription UTxOs into Hydra
- Mints “access slots” in Hydra
- Signs access receipts which the User can verify
Typical lifecycle
- User creates on-chain script UTxO with their funds (
User.create→ sign & submit tx). - Operator:
- Initializes Hydra head (
Operator.initialize) - Commits the user's script UTxO into Hydra (
Operator.createUserSub)
- Initializes Hydra head (
- User registers that UTxO with the subscription node (
User.init). - Whenever access is needed:
- Operator generates access signature (
Operator.createAccess/getAccess) - Dapp verifies the access (
User.verifyAccess).
- Operator generates access signature (
- User can:
- Check balance & subscription state (
User.fetchBalance) - Pause or cancel (
User.pause,User.cancel) - Withdraw funds from L1 script back to their wallet (
User.withdrawFromL1).
- Check balance & subscription state (
Operator
import { Operator } from "@nucastio/sub-hydra";
Constructor
const operator = new Operator({
wallet: {
network: 0, // 0 = Preprod, 1 = Mainnet
key: {
type: "cli",
payment: "KEY"
},
},
blockfrostApiKey: process.env.BLOCKFROST_API_KEY!,
hydraURL: "http://localhost:4001", // Example Hydra head URL
});
initialize(amountOrUtxo)
await operator.initialize(10_000_000);
// or:
await operator.initialize({ txHash, outputIndex });
After successful commit, operator.status becomes "INITIALIZED".
connect()
await operator.connect();
- Connects to a already-open Hydra head.
Use this if your Hydra head is already running and initialized elsewhere.
createUserSub(utxo: UtxoReference)
const commitTxHash = await operator.createUserSub({
txHash: "user-script-utxo-txhash",
outputIndex: 0,
});
- Commits a user subscription script UTxO into the Hydra head.
closeSub()
await operator.closeSub();
- Closes the Hydra head via
hydraProvider.close(). - Waits for head status
Closed. - Triggers
fanout()to settle funds back to L1. - Sets
status = "NOT_INITIALIZED".
createAccess(address, subscriptionProviderPubKey, intervalBetweenPaymentsPosixTime, asset)
const signature = await operator.createAccess(
userBech32Address,
subscriptionProviderPubKey,
2 * 60 * 1000, // interval in POSIX milliseconds (example: 2 minutes)
{
unit: "lovelace",
quantity: "1000000", // 1 ADA
}
);
If the tx is successful, returns a access signature.
This is the access proof to be used with User.verifyAccess.
getAccess(address, subscriptionProviderPubKey)
const signature = await operator.getAccess(
userBech32Address,
subscriptionProviderPubKey
);
- returns a access signature.
- If expired, returns
null.
mergePayouts(subscriptionProviderPubKey: string)
const txCbor = await operator.mergePayouts(subscriptionProviderPubKey);
- Fetches all UTxOs at the subscription provider address in Hydra.
- Builds a tx that sums up them into one output back to the same address.
- Returns the transaction CBOR.
You can submit this CBOR with the Hydra provider as needed.
parseDatum(datum: string)
const parsed = await operator.parseDatum(cborDatumString);
Returns:
{
subscriberPubKey: string;
subscriptionProviderPubKey: string;
operatorPubKey: string;
nextPayment: bigint;
lastPayment: bigint;
interval: bigint;
asset: {
unit: string; // "lovelace" or policyId.assetName
quantity: bigint; // token quantity or ADA amount
};
}
Use this to interpret script datums on-chain or in Hydra.
User API
import { User } from "@nucastio/sub-hydra";
Constructor
const user = new User({
hydraHeadProviderPubKey: "<operator-pubkey-hash>",
subscriptionProviderPubKey: "<subscription-provider-pubkey-hash>",
blockfrostApiKey: process.env.BLOCKFROST_API_KEY!,
subscriptionNodeURL: "https://subscription-node.example.com",
network: "Preprod", // or "Mainnet"
});
networkis"Mainnet"or"Preprod"and is internally mapped to1or0.
create(address, asset)
Builds a L1 transaction that locks user funds into the subscription script.
const scriptTxCbor = await user.create(userBech32Address, {
unit: "lovelace",
quantity: "5000000", // 5 ADA
});
Returns transaction CBOR
init(scriptTx: ScriptTxParams)
Registers the script UTxO with the subscription node.
const result = await user.init({
txHash: "your-subscription-tx-hash",
outputIndex: 0,
});
// result: { added: boolean }
subscribe(address, subscriptionProviderPubKey)
Requests an access signature from the subscription node.
const { signature } = await user.subscribe(
userBech32Address,
subscriptionProviderPubKey
);
// signature: { key: string; signature: string; }
verifyAccess(sig, receipt)
Verifies a signature produced
const isValid = await user.verifyAccess(signatureFromOperator, {
subscriberPubKey: "<subscriber-pubkey-hash>",
subscriptionProviderPubKey: "<subscription-provider-pubkey-hash>",
nextPaymentPosixTime: nextPayment,
});
withdrawFromL1(address)
Builds a transaction to withdraw all funds from the subscription script back to the user’s address (on L1).
const withdrawTxCbor = await user.withdrawFromL1(userBech32Address);
// Sign and submit this tx with the user's wallet.
access(address, subscriptionProviderPubKey)
providesan access signature via the subscription node.
const accessResult = await user.access(
userBech32Address,
subscriptionProviderPubKey
);
Body:fetchBalance(address)
Fetches user subscription state & UTxOs via the subscription node.
const balance = await user.fetchBalance(userBech32Address);
/*
{
utxos: [{
assets: [{ unit: string; quantity: string }[]],
subscription: {
subscriberPubKey: string;
subscriptionProviderPubKey: string;
operatorPubKey: string;
nextPayment: number;
lastPayment: number;
interval: number;
asset: { unit: string; quantity: number };
} | null;
}]
}
*/
pause(address, subscriptionProviderPubKey)
Pauses the user’s subscription.
const { paused, message } = await user.pause(
userBech32Address,
subscriptionProviderPubKey
);
cancel(address, subscriptionProviderPubKey)
Cancels the user’s subscription.
const { cancelled, message } = await user.cancel(
userBech32Address,
subscriptionProviderPubKey
);
End-to-End Example (Minimal)
import { Operator, User } from "@nucastio/sub-hydra";
const operator = new Operator({
wallet: {
network: 0, // Preprod
key: {
type: "mnemonic",
words: process.env.OPERATOR_MNEMONIC!.split(" "),
},
},
blockfrostApiKey: process.env.BLOCKFROST_API_KEY!,
hydraURL: "http://localhost:4001",
});
await operator.initialize(20_000_000); // commit collateral 20 ADA into Hydra
const user = new User({
hydraHeadProviderPubKey: "<operator-pubkey-hash>",
subscriptionProviderPubKey: "<subscription-provider-pubkey-hash>",
blockfrostApiKey: process.env.BLOCKFROST_API_KEY!,
subscriptionNodeURL: "https://subscription-node.example.com",
network: "Preprod",
});
// 1. User creates subscription script UTxO
const createTxCbor = await user.create("<user-bech32-address>", {
unit: "lovelace",
quantity: "5000000",
} as Asset);
// -> sign & submit, get txHash & outputIndex
// 2. Operator commits user UTxO into Hydra
await operator.createUserSub({ txHash: "<txHash>", outputIndex: 0 });
// 3. User registers script with subscription node
await user.init({ txHash: "<txHash>", outputIndex: 0 });
// 4. When user wants access:
const accessSig = await operator.createAccess(
"<user-bech32-address>",
"<subscription-provider-pubkey-hash>",
2 * 60 * 1000,
{ unit: "lovelace", quantity: "1000000" } as Asset
);
// 5. DApp verifies the received signature:
const isValid = await user.verifyAccess(accessSig!, {
subscriberPubKey: "<subscriber-pubkey-hash>",
subscriptionProviderPubKey: "<subscription-provider-pubkey-hash>",
nextPaymentPosixTime: Date.now() + 2 * 60 * 1000,
});
