@openzeppelin/miden-multisig-client
v0.13.4
Published
TypeScript SDK for Miden multisig accounts with GUARDIAN integration
Downloads
541
Keywords
Readme
@openzeppelin/miden-multisig-client
TypeScript SDK for private multisignature workflows on Miden. This package wraps the on-chain multisig contracts plus Guardian coordination so you can:
- Create multisig accounts, register them with a GUARDIAN, and keep state off-chain
- Propose, sign, and execute transactions with threshold enforcement
- Export/import proposals as files for sharing using side channels
How Private Multisigs & GUARDIAN Work
Miden multisig accounts store their authentication logic on-chain, but their state (signers, metadata, proposals) is kept private. GUARDIAN acts as a coordination server:
- A proposer pushes a delta (transaction plan) to Guardian. GUARDIAN tracks who signed and emits an ack signature once the threshold is met.
- Cosigners fetch pending deltas, verify details locally, sign the transaction summary, and push signatures back to GUARDIAN.
- Once ready, any cosigner builds the final transaction using all cosigner signatures + the GUARDIAN ack, executes it on-chain.
Installation
npm install @openzeppelin/miden-multisig-client @demox-labs/miden-sdkSetup
import { MultisigClient, FalconSigner } from '@openzeppelin/miden-multisig-client';
import { WebClient, SecretKey } from '@demox-labs/miden-sdk';
// Initialize Miden WebClient
const webClient = await WebClient.createClient('https://rpc.testnet.miden.io:443');
// Create a signer from your secret key
const secretKey = SecretKey.rpoFalconWithRNG(seed);
const signer = new FalconSigner(secretKey);
// Create MultisigClient
const client = new MultisigClient(webClient, {
guardianEndpoint: 'http://localhost:3000',
midenRpcEndpoint: 'https://rpc.testnet.miden.io:443',
});Usage
Get GUARDIAN Public Key
Before creating a multisig, get the GUARDIAN server's public key commitment:
const guardianCommitment = await client.guardianClient.getPubkey();Create a Multisig Account
const config = {
threshold: 2, // Require 2 signatures
signerCommitments: [
signer.commitment, // Your commitment
otherSigner.commitment, // Cosigner's commitment
],
guardianCommitment,
};
const multisig = await client.create(config, signer);
console.log('Account ID:', multisig.accountId);Register on GUARDIAN
After creating the account, register it on the GUARDIAN server:
await multisig.registerOnGuardian();Load an Existing Multisig
The configuration is automatically detected from the account's on-chain storage:
const multisig = await client.load(accountId, signer);Fetch Account State
const state = await multisig.fetchState();
console.log('Commitment:', state.commitment);
console.log('Created:', state.createdAt);Create a Proposal (Add Signer)
// Create a proposal to add a new signer
const nonce = Math.floor(Math.random() * 1_000_000_000);
const proposal = await multisig.createAddSignerProposal(
webClient,
newSignerCommitment, // Commitment of signer to add
nonce, // Optional nonce (random value)
3, // Optional new threshold
);
console.log('Proposal ID:', proposal.id);Sign a Proposal
const signedProposal = await multisig.signProposal(proposal.id);
console.log('Signatures:', signedProposal.signatures.length);Sync Proposals
Fetches proposals from the GUARDIAN server and updates local state:
const proposals = await multisig.syncProposals();
for (const p of proposals) {
console.log(`${p.id}: ${p.status}`);
}Check Proposal Status
Returns cached proposals without making a network request:
const proposals = multisig.listProposals();
for (const p of proposals) {
if (p.status === 'pending') {
console.log(`Pending signatures: ${p.signatures.length}`);
} else if (p.status === 'ready') {
console.log('Ready to execute!');
}
}Execute a Proposal
When a proposal has enough signatures:
if (proposal.status === 'ready') {
await multisig.executeProposal(proposal.id, webClient);
console.log('Transaction executed on-chain!');
}Export Proposal for Offline Signing
const exported = await multisig.exportProposal(proposal.id);
// Send `exported` to offline signer
console.log('TX Summary:', exported.txSummaryBase64);
console.log('Commitment to sign:', exported.commitment);Import and Sign a Proposal Offline
Imported proposals are now validated against their transaction summary before they are cached or signed:
const imported = await multisig.importProposal(jsonFromCosigner);
const signedJson = await multisig.signProposalOffline(imported.id);
console.log(signedJson);Transaction Utilities
The package also exports utility functions for building transactions:
import {
normalizeHexWord,
hexToUint8Array,
signatureHexToBytes,
buildSignatureAdviceEntry,
} from '@openzeppelin/miden-multisig-client';
// Normalize hex for Word.fromHex (pads to 64 chars)
const normalized = normalizeHexWord('abc123');
// => '0x0000...abc123'
// Convert hex to bytes
const bytes = hexToUint8Array('deadbeef');
// => Uint8Array([0xde, 0xad, 0xbe, 0xef])
// Add auth scheme prefix to signature
const sigBytes = signatureHexToBytes(signatureHex);
// => Uint8Array with 0x00 prefix (RpoFalcon512)Testing
npm test # Run tests once
npm run test:watch # Run tests in watch mode