diff --git a/contract_manager/.gitignore b/contract_manager/.gitignore index c3af8579..6f0a0257 100644 --- a/contract_manager/.gitignore +++ b/contract_manager/.gitignore @@ -1 +1,3 @@ lib/ +.cache* +docs diff --git a/contract_manager/README.md b/contract_manager/README.md index 77bcec8d..ebb740ff 100644 --- a/contract_manager/README.md +++ b/contract_manager/README.md @@ -7,3 +7,21 @@ It has the following structure: - `store` contains all the necessary information for registered chains and deployed contracts - `scripts` contains utility scripts to interact with the contract manager and accomplish common tasks - `src` contains the contract manager code + +# Main Entities + +Contract Manager has base classes which you can use to interact with the following entities: + +- Chain +- PythContract +- WormholeContract + +Each of these entities has a specialized class for each supported chain (EVM/Cosmos/Aptos/Sui). + +# Docs + +You can generate the docs by running `npx typedoc src/index.ts` from this directory. Open the docs by opening `docs/index.html` in your browser. + +# Scripts + +You can run the scripts by executing `npx ts-node scripts/.ts` from this directory. diff --git a/contract_manager/package.json b/contract_manager/package.json index 5cf24efa..7d1d712f 100644 --- a/contract_manager/package.json +++ b/contract_manager/package.json @@ -20,18 +20,19 @@ "url": "git+https://github.com/pyth-network/pyth-crosschain.git" }, "dependencies": { - "@mysten/sui.js": "^0.37.1", "@certusone/wormhole-sdk": "^0.9.8", + "@injectivelabs/networks": "1.0.68", + "@mysten/sui.js": "^0.37.1", "@pythnetwork/cosmwasm-deploy-tools": "*", "@pythnetwork/price-service-client": "*", "@pythnetwork/pyth-sui-js": "*", - "@injectivelabs/networks": "1.0.68", "aptos": "^1.5.0", "bs58": "^5.0.0", "ts-node": "^10.9.1", "typescript": "^4.9.3" }, "devDependencies": { - "prettier": "^2.6.2" + "prettier": "^2.6.2", + "typedoc": "^0.25.7" } } diff --git a/contract_manager/scripts/check_proposal.ts b/contract_manager/scripts/check_proposal.ts index aa0cf60e..1117b5f1 100644 --- a/contract_manager/scripts/check_proposal.ts +++ b/contract_manager/scripts/check_proposal.ts @@ -63,7 +63,6 @@ async function main() { for (const chain of Object.values(DefaultStore.chains)) { if ( chain instanceof EvmChain && - chain.isMainnet() === (cluster === "mainnet-beta") && chain.wormholeChainName === instruction.governanceAction.targetChainId ) { diff --git a/contract_manager/scripts/execute_vaas.ts b/contract_manager/scripts/execute_vaas.ts new file mode 100644 index 00000000..504b4ae6 --- /dev/null +++ b/contract_manager/scripts/execute_vaas.ts @@ -0,0 +1,88 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { DefaultStore } from "../src/store"; +import { SubmittedWormholeMessage, Vault } from "../src/governance"; +import { parseVaa } from "@certusone/wormhole-sdk"; +import { decodeGovernancePayload } from "xc_admin_common"; +import { executeVaa } from "../src/executor"; +import { toPrivateKey } from "../src"; + +const parser = yargs(hideBin(process.argv)) + .usage( + "Tries to execute all vaas on a vault.\n" + + "Useful for batch upgrades.\n" + + "Usage: $0 --vault --private-key --offset [--dryrun]" + ) + .options({ + vault: { + type: "string", + default: "mainnet", + choices: ["mainnet", "devnet"], + desc: "Which vault to use for fetching VAAs", + }, + "private-key": { + type: "string", + demandOption: true, + desc: "Private key to sign the transactions executing the governance VAAs. Hex format, without 0x prefix.", + }, + offset: { + type: "number", + demandOption: true, + desc: "Offset to use from the last executed sequence number", + }, + dryrun: { + type: "boolean", + default: false, + desc: "Whether to execute the VAAs or just print them", + }, + }); + +async function main() { + const argv = await parser.argv; + let vault: Vault; + if (argv.vault === "mainnet") { + vault = + DefaultStore.vaults[ + "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj" + ]; + } else { + vault = + DefaultStore.vaults[ + "devnet_6baWtW1zTUVMSJHJQVxDUXWzqrQeYBr6mu31j3bTKwY3" + ]; + } + console.log("Executing VAAs for vault", vault.getId()); + console.log( + "Executing VAAs for emitter", + (await vault.getEmitter()).toBase58() + ); + const lastSequenceNumber = await vault.getLastSequenceNumber(); + const startSequenceNumber = lastSequenceNumber - argv.offset; + console.log( + `Going from sequence number ${startSequenceNumber} to ${lastSequenceNumber}` + ); + for ( + let seqNumber = startSequenceNumber; + seqNumber <= lastSequenceNumber; + seqNumber++ + ) { + const submittedWormholeMessage = new SubmittedWormholeMessage( + await vault.getEmitter(), + seqNumber, + vault.cluster + ); + const vaa = await submittedWormholeMessage.fetchVaa(); + const decodedAction = decodeGovernancePayload(parseVaa(vaa).payload); + if (!decodedAction) { + console.log("Skipping unknown action for vaa ", seqNumber); + continue; + } + console.log("Executing vaa", seqNumber); + console.log(decodedAction); + if (!argv.dryrun) { + await executeVaa(toPrivateKey(argv["private-key"]), vaa); + } + } +} + +main(); diff --git a/contract_manager/scripts/list_evm_contracts.ts b/contract_manager/scripts/list_evm_contracts.ts new file mode 100644 index 00000000..51794d4a --- /dev/null +++ b/contract_manager/scripts/list_evm_contracts.ts @@ -0,0 +1,42 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { + AptosContract, + CosmWasmContract, + DefaultStore, + EvmContract, +} from "../src"; + +const parser = yargs(hideBin(process.argv)) + .usage("Usage: $0") + .options({ + testnet: { + type: "boolean", + default: false, + desc: "Fetch testnet contract fees instead of mainnet", + }, + }); + +async function main() { + const argv = await parser.argv; + const entries = []; + for (const contract of Object.values(DefaultStore.contracts)) { + if (contract.getChain().isMainnet() === argv.testnet) continue; + if (contract instanceof EvmContract) { + try { + const version = await contract.getVersion(); + entries.push({ + chain: contract.getChain().getId(), + contract: contract.address, + version: version, + }); + console.log(`Fetched version for ${contract.getId()}`); + } catch (e) { + console.error(`Error fetching version for ${contract.getId()}`, e); + } + } + } + console.table(entries); +} + +main(); diff --git a/contract_manager/scripts/upgrade_evm_contracts.ts b/contract_manager/scripts/upgrade_evm_contracts.ts new file mode 100644 index 00000000..c56138de --- /dev/null +++ b/contract_manager/scripts/upgrade_evm_contracts.ts @@ -0,0 +1,127 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { DefaultStore, EvmChain, loadHotWallet, toPrivateKey } from "../src"; +import { existsSync, readFileSync, writeFileSync } from "fs"; + +const CACHE_FILE = ".cache-upgrade-evm"; + +const parser = yargs(hideBin(process.argv)) + .usage( + "Deploys a new PythUpgradable contract to a set of chains and creates a governance proposal for it.\n" + + `Uses a cache file (${CACHE_FILE}) to avoid deploying contracts twice\n` + + "Usage: $0 --chain --chain --private-key --ops-key-path --std-output " + ) + .options({ + testnet: { + type: "boolean", + default: false, + desc: "Upgrade testnet contracts instead of mainnet", + }, + "all-chains": { + type: "boolean", + default: false, + desc: "Upgrade the contract on all chains. Use with --testnet flag to upgrade all testnet contracts", + }, + chain: { + type: "array", + string: true, + desc: "Chains to upgrade the contract on", + }, + "private-key": { + type: "string", + demandOption: true, + desc: "Private key to use for the deployment", + }, + "ops-key-path": { + type: "string", + demandOption: true, + desc: "Path to the private key of the proposer to use for the operations multisig governance proposal", + }, + "std-output": { + type: "string", + demandOption: true, + desc: "Path to the standard JSON output of the pyth contract (build artifact)", + }, + }); + +async function run_if_not_cached( + cache_key: string, + fn: () => Promise +): Promise { + const cache = existsSync(CACHE_FILE) + ? JSON.parse(readFileSync(CACHE_FILE, "utf8")) + : {}; + if (cache[cache_key]) { + return cache[cache_key]; + } + const result = await fn(); + cache[cache_key] = result; + writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2)); + return result; +} + +async function main() { + const argv = await parser.argv; + const selectedChains: EvmChain[] = []; + + if (argv.allChains && argv.chain) + throw new Error("Cannot use both --all-chains and --chain"); + if (!argv.allChains && !argv.chain) + throw new Error("Must use either --all-chains or --chain"); + for (const chain of Object.values(DefaultStore.chains)) { + if (!(chain instanceof EvmChain)) continue; + if ( + (argv.allChains && chain.isMainnet() !== argv.testnet) || + argv.chain?.includes(chain.getId()) + ) + selectedChains.push(chain); + } + if (argv.chain && selectedChains.length !== argv.chain.length) + throw new Error( + `Some chains were not found ${selectedChains + .map((chain) => chain.getId()) + .toString()}` + ); + for (const chain of selectedChains) { + if (chain.isMainnet() != selectedChains[0].isMainnet()) + throw new Error("All chains must be either mainnet or testnet"); + } + + const vault = + DefaultStore.vaults[ + "mainnet-beta_FVQyHcooAtThJ83XFrNnv74BcinbRH3bRmfFamAHBfuj" + ]; + + console.log("Using cache file", CACHE_FILE); + console.log( + "Upgrading on chains", + selectedChains.map((c) => c.getId()) + ); + + const payloads: Buffer[] = []; + for (const chain of selectedChains) { + const artifact = JSON.parse(readFileSync(argv["std-output"], "utf8")); + console.log("Deploying contract to", chain.getId()); + const address = await run_if_not_cached(`deploy-${chain.getId()}`, () => { + return chain.deploy( + toPrivateKey(argv["private-key"]), + artifact["abi"], + artifact["bytecode"], + [] + ); + }); + console.log(`Deployed contract at ${address} on ${chain.getId()}`); + payloads.push( + chain.generateGovernanceUpgradePayload(address.replace("0x", "")) + ); + } + + console.log("Using vault at for proposal", vault.getId()); + const wallet = await loadHotWallet(argv["ops-key-path"]); + console.log("Using wallet ", wallet.publicKey.toBase58()); + await vault.connect(wallet); + const proposal = await vault.proposeWormholeMessage(payloads); + console.log("Proposal address", proposal.address.toBase58()); +} + +main(); diff --git a/contract_manager/src/executor.ts b/contract_manager/src/executor.ts index 9123bbf1..3526a175 100644 --- a/contract_manager/src/executor.ts +++ b/contract_manager/src/executor.ts @@ -23,8 +23,16 @@ export async function executeVaa(senderPrivateKey: PrivateKey, vaa: Buffer) { parsedVaa.emitterAddress.toString("hex") && governanceSource.emitterChain === parsedVaa.emitterChain ) { - // TODO: check governance sequence number as well + const lastExecutedSequence = + await contract.getLastExecutedGovernanceSequence(); + if (lastExecutedSequence >= parsedVaa.sequence) { + console.log( + `Skipping on contract ${contract.getId()} as it was already executed` + ); + continue; + } await contract.executeGovernanceInstruction(senderPrivateKey, vaa); + console.log(`Executed on contract ${contract.getId()}`); } } } diff --git a/contract_manager/src/governance.ts b/contract_manager/src/governance.ts index 1c04e929..4c104c85 100644 --- a/contract_manager/src/governance.ts +++ b/contract_manager/src/governance.ts @@ -200,13 +200,16 @@ export class WormholeEmitter { } } -export class WormholeMultiSigTransaction { +export class WormholeMultisigProposal { constructor( public address: PublicKey, public squad: SquadsMesh, public cluster: PythCluster ) {} + /** + * Gets the current state of the proposal which can be "active", "draft", "executed", etc. + */ async getState() { const proposal = await this.squad.getTransaction(this.address); // Converts the status object to a string e.g @@ -214,6 +217,10 @@ export class WormholeMultiSigTransaction { return Object.keys(proposal.status)[0]; } + /** + * Executes the proposal and returns the wormhole messages that were sent + * The proposal must be already approved. + */ async execute(): Promise { const proposal = await this.squad.getTransaction(this.address); const signatures = await executeProposal( @@ -239,6 +246,10 @@ export class WormholeMultiSigTransaction { } } +/** + * A vault represents a pyth multisig governance realm which exists in solana mainnet or testnet. + * It can be used for proposals to send wormhole messages to the wormhole bridge. + */ export class Vault extends Storable { static type = "vault"; key: PublicKey; @@ -276,6 +287,11 @@ export class Vault extends Storable { }; } + /** + * Connects the vault to a wallet that can be used to submit proposals + * The wallet should be a multisig signer of the vault + * @param wallet + */ public connect(wallet: Wallet): void { this.squad = SquadsMesh.endpoint( getPythClusterApiUrl(this.cluster), @@ -288,6 +304,9 @@ export class Vault extends Storable { return this.squad; } + /** + * Gets the emitter address of the vault + */ public async getEmitter() { const squad = SquadsMesh.endpoint( getPythClusterApiUrl(this.cluster), @@ -296,10 +315,33 @@ export class Vault extends Storable { return squad.getAuthorityPDA(this.key, 1); } + /** + * Gets the last sequence number of the vault emitter + * This is used to determine the sequence number of the next wormhole message + * Fetches the sequence number from the wormholescan API + * @returns the last sequence number + */ + public async getLastSequenceNumber(): Promise { + const rpcUrl = WORMHOLE_API_ENDPOINT[this.cluster]; + const emitter = await this.getEmitter(); + const response = await fetch( + `${rpcUrl}/api/v1/vaas/1/${emitter.toBase58()}` + ); + const { data } = await response.json(); + return data[0].sequence; + } + + /** + * Proposes sending an array of wormhole messages to the wormhole bridge + * Requires a wallet to be connected to the vault + * + * @param payloads the payloads to send to the wormhole bridge + * @param proposalAddress if specified, will continue an existing proposal + */ public async proposeWormholeMessage( payloads: Buffer[], proposalAddress?: PublicKey - ): Promise { + ): Promise { const squad = this.getSquadOrThrow(); const multisigVault = new MultisigVault( squad.wallet, @@ -313,14 +355,19 @@ export class Vault extends Storable { squad.wallet.publicKey, proposalAddress ); - return new WormholeMultiSigTransaction(txAccount, squad, this.cluster); + return new WormholeMultisigProposal(txAccount, squad, this.cluster); } } -export async function loadHotWallet(wallet: string): Promise { +/** + * Loads a solana wallet from a file. The file should contain the secret key in array of integers format + * This wallet can be used to connect to a vault and submit proposals + * @param walletPath path to the wallet file + */ +export async function loadHotWallet(walletPath: string): Promise { return new NodeWallet( Keypair.fromSecretKey( - Uint8Array.from(JSON.parse(readFileSync(wallet, "ascii"))) + Uint8Array.from(JSON.parse(readFileSync(walletPath, "ascii"))) ) ); } diff --git a/contract_manager/src/store.ts b/contract_manager/src/store.ts index 8a95a3e8..3448433b 100644 --- a/contract_manager/src/store.ts +++ b/contract_manager/src/store.ts @@ -146,4 +146,7 @@ export class Store { } } +/** + * DefaultStore loads all the contracts and chains from the store directory and provides a single point of access to them. + */ export const DefaultStore = new Store(`${__dirname}/../store`); diff --git a/package-lock.json b/package-lock.json index 495ad055..ae58b9c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,8 @@ "typescript": "^4.9.3" }, "devDependencies": { - "prettier": "^2.6.2" + "prettier": "^2.6.2", + "typedoc": "^0.25.7" } }, "contract_manager/node_modules/@certusone/wormhole-sdk": { @@ -20839,6 +20840,12 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -36503,6 +36510,12 @@ "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -36605,6 +36618,18 @@ "node": ">=0.10.0" } }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/mcl-wasm": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", @@ -47697,6 +47722,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/shx": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", @@ -51734,6 +51771,51 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedoc": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.7.tgz", + "integrity": "sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.7" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typeforce": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", @@ -52372,6 +52454,18 @@ "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "node_modules/vuvuzela": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz", @@ -76899,6 +76993,12 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, + "ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -79635,6 +79735,7 @@ "bs58": "^5.0.0", "prettier": "^2.6.2", "ts-node": "^10.9.1", + "typedoc": "^0.25.7", "typescript": "^4.9.3" }, "dependencies": { @@ -89790,6 +89891,12 @@ "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -89867,6 +89974,12 @@ "object-visit": "^1.0.0" } }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true + }, "mcl-wasm": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", @@ -98959,6 +99072,18 @@ } } }, + "shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "requires": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "shx": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", @@ -102108,6 +102233,38 @@ "is-typedarray": "^1.0.0" } }, + "typedoc": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.7.tgz", + "integrity": "sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow==", + "dev": true, + "requires": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.7" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "typeforce": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", @@ -102613,6 +102770,18 @@ "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, "vuvuzela": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz",