-
Notifications
You must be signed in to change notification settings - Fork 2
Fix Node.js compatibility for CJS consumers and WASM initialization #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kaleababayneh
wants to merge
5
commits into
LumeraProtocol:master
Choose a base branch
from
kaleababayneh:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
06e33f8
Fix Node.js CJS entry point and lazy-load WASM for Node compatibility
kaleababayneh 4c2a7a9
Address review feedback: Use process.versions.node instead of typeof …
kaleababayneh 13d2d6b
remove package.json
kaleababayneh 1a8cde3
Address review feedback: remove import.meta.url from CJS, preserve sy…
kaleababayneh f8a7f83
Update upload example: use existing styling and add step logging
kaleababayneh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -173,4 +173,5 @@ coverage/ | |
|
|
||
| # Vitest | ||
| coverage.data | ||
| coverage/ | ||
| coverage/package-lock.json | ||
| package-lock.json | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "name": "node", | ||
| "version": "1.0.0", | ||
| "description": "This directory contains Node.js examples demonstrating the Lumera SDK capabilities.", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC", | ||
| "type": "commonjs" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,234 +1,123 @@ | ||
| /** | ||
| * Advanced Upload Example: Direct Message Composition | ||
| * | ||
| * This example demonstrates how to use the Telescope-generated message composers | ||
| * directly for more granular control over the upload workflow: | ||
| * | ||
| * 1. Build file metadata manually | ||
| * 2. Create messages using generated MessageComposer | ||
| * 3. Simulate, sign, and broadcast transactions | ||
| * 4. Complete the action with finalization | ||
| * | ||
| * Cascade Upload Example: LumeraClient Facade | ||
| * | ||
| * This example demonstrates the high-level upload workflow using the | ||
| * LumeraClient facade, which handles RaptorQ encoding, supernode | ||
| * communication, and on-chain action registration automatically: | ||
| * | ||
| * 1. Initialize wallets (Direct + Amino for ADR-036 signing) | ||
| * 2. Create a LumeraClient with testnet preset | ||
| * 3. Upload a file via the Cascade uploader | ||
| * | ||
| * Prerequisites: | ||
| * - Set the MNEMONIC environment variable with a valid 24-word mnemonic | ||
| * - Set the MNEMONIC environment variable with a valid mnemonic | ||
| * - Ensure the account has sufficient balance for transaction fees | ||
| * - Have a file to upload (e.g., example.bin) | ||
| * | ||
| * | ||
| * Usage: | ||
| * MNEMONIC="your mnemonic here" npx tsx examples/node/upload.ts | ||
| */ | ||
|
|
||
| import { makeBlockchainClient } from "../../src/blockchain/client"; | ||
| import { createLumeraClient } from "../../src"; | ||
| import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; | ||
| import { lumera } from "../../src/codegen"; | ||
| import { calculateCascadeFee } from "../../src/blockchain/messages"; | ||
| import fs from "node:fs"; | ||
| import crypto from "node:crypto"; | ||
| import { Secp256k1HdWallet, makeSignDoc as makeAminoSignDoc } from "@cosmjs/amino"; | ||
|
|
||
| async function main() { | ||
| console.log("=".repeat(60)); | ||
| console.log("Lumera SDK - Direct Message Composition Example"); | ||
| console.log("Lumera SDK - Cascade Upload Example"); | ||
| console.log("=".repeat(60)); | ||
|
|
||
| // ============================================================================ | ||
| // STEP 1: Initialize wallet and blockchain client | ||
| // STEP 1: Initialize wallets | ||
| // ============================================================================ | ||
| console.log("\n[Step 1] Setting up wallet and blockchain client..."); | ||
| console.log("\n[Step 1] Setting up wallets..."); | ||
|
|
||
| if (!process.env.MNEMONIC) { | ||
| throw new Error("Set MNEMONIC environment variable"); | ||
| } | ||
|
|
||
| const wallet = await DirectSecp256k1HdWallet.fromMnemonic(process.env.MNEMONIC, { | ||
| prefix: "lumera" | ||
| const directWallet = await DirectSecp256k1HdWallet.fromMnemonic(process.env.MNEMONIC, { | ||
| prefix: "lumera", | ||
| }); | ||
| const [account] = await wallet.getAccounts(); | ||
| console.log(`✓ Using address: ${account.address}`); | ||
|
|
||
| const chain = await makeBlockchainClient({ | ||
| rpcUrl: "https://rpc.testnet.lumera.io", | ||
| lcdUrl: "https://lcd.testnet.lumera.io", | ||
| chainId: "lumera-testnet-2", | ||
| signer: wallet, | ||
| address: account.address, | ||
| gasPrice: "0.025ulume" | ||
| const aminoWallet = await Secp256k1HdWallet.fromMnemonic(process.env.MNEMONIC, { | ||
| prefix: "lumera", | ||
| }); | ||
|
|
||
| console.log(`✓ Connected to chain: ${await chain.getChainId()}`); | ||
|
|
||
| // ============================================================================ | ||
| // STEP 2: Query blockchain parameters | ||
| // ============================================================================ | ||
| console.log("\n[Step 2] Querying action module parameters..."); | ||
|
|
||
| const params = await chain.Action.getParams(); | ||
| console.log(` rq_ids_max: ${params.rq_ids_max}`); | ||
| console.log(` rq_ids_ic: ${params.rq_ids_ic}`); | ||
| console.log(` fee_base: ${params.fee_base}`); | ||
| console.log(` fee_per_kb: ${params.fee_per_kb}`); | ||
|
|
||
| // ============================================================================ | ||
| // STEP 3: Prepare file and metadata | ||
| // ============================================================================ | ||
| console.log("\n[Step 3] Preparing file metadata..."); | ||
|
|
||
| // For this example, create a sample file if it doesn't exist | ||
| const filePath = "./example.bin"; | ||
| let fileData: Buffer; | ||
|
|
||
| if (fs.existsSync(filePath)) { | ||
| fileData = fs.readFileSync(filePath); | ||
| console.log(`✓ Loaded existing file: ${filePath} (${fileData.length} bytes)`); | ||
| } else { | ||
| // Create a sample file | ||
| fileData = crypto.randomBytes(1024); // 1KB sample file | ||
| fs.writeFileSync(filePath, fileData); | ||
| console.log(`✓ Created sample file: ${filePath} (${fileData.length} bytes)`); | ||
| } | ||
|
|
||
| // Calculate file hash using SHA-256 | ||
| const fileHash = crypto.createHash("sha256").update(fileData).digest("hex"); | ||
| console.log(` File hash: ${fileHash}`); | ||
|
|
||
| // Generate action ID | ||
| const actionId = `example-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`; | ||
| console.log(` Action ID: ${actionId}`); | ||
|
|
||
| // Build metadata for Cascade action | ||
| const metadata = { | ||
| data_hash: fileHash, | ||
| file_name: "example.bin", | ||
| rq_ids_ic: params.rq_ids_ic, | ||
| signatures: "", // Would be populated with RaptorQ signatures in real use | ||
| public: false, | ||
| const [account] = await directWallet.getAccounts(); | ||
| console.log(` Address: ${account.address}`); | ||
|
|
||
| // Combine direct + amino wallets and add signArbitrary (ADR-036) for Cascade | ||
| const signer = { | ||
| getAccounts: () => directWallet.getAccounts(), | ||
| signDirect: (addr: string, doc: any) => directWallet.signDirect(addr, doc), | ||
| signAmino: (addr: string, doc: any) => aminoWallet.signAmino(addr, doc), | ||
| async signArbitrary(_chainId: string, signerAddress: string, data: string) { | ||
| const signDoc = makeAminoSignDoc( | ||
| [ | ||
| { | ||
| type: "sign/MsgSignData", | ||
| value: { | ||
| signer: signerAddress, | ||
| data: Buffer.from(data).toString("base64"), | ||
| }, | ||
| }, | ||
| ], | ||
| { gas: "0", amount: [] }, | ||
| "", // ADR-036 requires empty chain_id | ||
| "", | ||
| 0, | ||
| 0 | ||
| ); | ||
| const { signature } = await aminoWallet.signAmino(signerAddress, signDoc); | ||
| return { | ||
| signed: data, | ||
| signature: signature.signature, | ||
| pub_key: signature.pub_key, | ||
| }; | ||
| }, | ||
| }; | ||
|
|
||
| // Calculate price based on file size | ||
| const price = calculateCascadeFee(fileData.length, params.fee_base, params.fee_per_kb); | ||
| console.log(` Calculated fee: ${price} ulume`); | ||
|
|
||
| // Set expiration time (24 hours from now) | ||
| const expirationTime = Math.floor(Date.now() / 1000 + 86400).toString(); | ||
| console.log(" Wallets ready"); | ||
|
|
||
| // ============================================================================ | ||
| // STEP 4: Build and broadcast MsgRequestAction using generated composer | ||
| // STEP 2: Create LumeraClient | ||
| // ============================================================================ | ||
| console.log("\n[Step 4] Building MsgRequestAction with generated composer..."); | ||
|
|
||
| // Use the Telescope-generated message composer | ||
| const msgRequestAction = lumera.action.v1.MessageComposer.withTypeUrl.requestAction({ | ||
| creator: account.address, | ||
| actionType: "cascade", | ||
| metadata: JSON.stringify(metadata), | ||
| price, | ||
| expirationTime, | ||
| }); | ||
| console.log("\n[Step 2] Creating LumeraClient..."); | ||
|
|
||
| console.log(`✓ Message created with typeUrl: ${msgRequestAction.typeUrl}`); | ||
| console.log(` Action type: ${msgRequestAction.value.actionType}`); | ||
| console.log(` Price: ${msgRequestAction.value.price}`); | ||
|
|
||
| // Simulate transaction to estimate gas | ||
| console.log("\n Simulating transaction..."); | ||
| const gasEstimate = await chain.Tx.simulate(account.address, [msgRequestAction]); | ||
| console.log(` Estimated gas: ${gasEstimate}`); | ||
|
|
||
| // Broadcast the transaction | ||
| console.log("\n Broadcasting transaction..."); | ||
| const result = await chain.Tx.signAndBroadcast( | ||
| account.address, | ||
| [msgRequestAction], | ||
| { amount: [{ denom: "ulume", amount: "10000" }], gas: gasEstimate.toString() }, | ||
| "Request Cascade action" | ||
| ); | ||
|
|
||
| if (result.code !== 0) { | ||
| console.error(`✗ Transaction failed: ${result.rawLog}`); | ||
| process.exit(1); | ||
| } | ||
| const client = await createLumeraClient({ | ||
| preset: "testnet", | ||
| signer: signer as any, | ||
| address: account.address, | ||
| gasPrice: "0.025ulume", | ||
| }); | ||
|
|
||
| console.log(`✓ Transaction successful!`); | ||
| console.log(` TX Hash: ${result.transactionHash}`); | ||
| console.log(` Block Height: ${result.height}`); | ||
| console.log(" Connected to testnet"); | ||
|
|
||
| // ============================================================================ | ||
| // STEP 5: Build and broadcast MsgFinalizeAction | ||
| // STEP 3: Upload a file via Cascade | ||
| // ============================================================================ | ||
| console.log("\n[Step 5] Building MsgFinalizeAction with generated composer..."); | ||
| console.log("\n[Step 3] Uploading file via Cascade..."); | ||
|
|
||
| // In a real scenario, you would: | ||
| // 1. Upload the file data to supernodes | ||
| // 2. Get the RaptorQ IDs from supernodes | ||
| // 3. Include those in the finalize metadata | ||
| const file = new TextEncoder().encode("Hello, Lumera!"); | ||
| // Expiration must be at least 86400s from now; add buffer to avoid race with block time | ||
| const expirationTime = String(Math.floor(Date.now() / 1000) + 86400 + 600); | ||
|
|
||
| const finalizeMetadata = { | ||
| ...metadata, | ||
| rq_ids_max: params.rq_ids_max, | ||
| rq_ids: ["id1", "id2", "id3"], // Would be real RaptorQ IDs | ||
| }; | ||
| console.log(` File size: ${file.length} bytes`); | ||
| console.log(` Expiration: ${expirationTime}`); | ||
|
|
||
| // Use the Telescope-generated message composer | ||
| const msgFinalizeAction = lumera.action.v1.MessageComposer.withTypeUrl.finalizeAction({ | ||
| creator: account.address, | ||
| actionId, | ||
| actionType: "cascade", | ||
| metadata: JSON.stringify(finalizeMetadata), | ||
| const result = await client.Cascade.uploader.uploadFile(file, { | ||
| fileName: "hello.txt", | ||
| isPublic: true, | ||
| expirationTime, | ||
| taskOptions: { pollInterval: 2000, timeout: 300000 }, | ||
| }); | ||
|
|
||
| console.log(`✓ Message created with typeUrl: ${msgFinalizeAction.typeUrl}`); | ||
| console.log(` Action ID: ${msgFinalizeAction.value.actionId}`); | ||
|
|
||
| // Simulate and broadcast finalize transaction | ||
| console.log("\n Simulating finalize transaction..."); | ||
| const finalizeGas = await chain.Tx.simulate(account.address, [msgFinalizeAction]); | ||
| console.log(` Estimated gas: ${finalizeGas}`); | ||
|
|
||
| console.log("\n Broadcasting finalize transaction..."); | ||
| const finalizeResult = await chain.Tx.signAndBroadcast( | ||
| account.address, | ||
| [msgFinalizeAction], | ||
| { amount: [{ denom: "ulume", amount: "10000" }], gas: finalizeGas.toString() }, | ||
| "Finalize Cascade action" | ||
| ); | ||
|
|
||
| if (finalizeResult.code !== 0) { | ||
| console.error(`✗ Finalize transaction failed: ${finalizeResult.rawLog}`); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| console.log(`✓ Finalize transaction successful!`); | ||
| console.log(` TX Hash: ${finalizeResult.transactionHash}`); | ||
| console.log(` Block Height: ${finalizeResult.height}`); | ||
|
|
||
| // ============================================================================ | ||
| // STEP 6: Verify action on blockchain | ||
| // ============================================================================ | ||
| console.log("\n[Step 6] Verifying action on blockchain..."); | ||
|
|
||
| try { | ||
| const action = await chain.Action.getAction(actionId); | ||
| console.log(`✓ Action found on chain:`); | ||
| console.log(` Action ID: ${action.actionId}`); | ||
| console.log(` Action Type: ${action.actionType}`); | ||
| console.log(` Creator: ${action.creator}`); | ||
| console.log(` State: ${action.state}`); | ||
| } catch (error) { | ||
| console.log(` Note: Action query may fail if not yet indexed`); | ||
| } | ||
|
|
||
| console.log("\n" + "=".repeat(60)); | ||
| console.log("SUCCESS! Demonstrated direct message composition workflow"); | ||
| console.log("Upload complete!"); | ||
| console.log("=".repeat(60)); | ||
| console.log("\nKey takeaways:"); | ||
| console.log(" - Used lumera.action.v1.MessageComposer.withTypeUrl for messages"); | ||
| console.log(" - Demonstrated both RequestAction and FinalizeAction"); | ||
| console.log(" - Showed gas estimation and transaction broadcasting"); | ||
| console.log(" - This is the low-level approach for advanced use cases"); | ||
| console.log("\nFor simpler workflows, use the LumeraClient facade instead!"); | ||
| console.log(result); | ||
| } | ||
|
|
||
| main().catch((error) => { | ||
| console.error("\n✗ Fatal error:"); | ||
| console.error("\nFatal error:"); | ||
| console.error(error); | ||
| process.exit(1); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original file ended with
coverage/and no trailing newline. Whenpackage-lock.jsonwas appended, it got concatenated onto that line, turningcoverage/intocoverage/package-lock.json. No functional impact sincecoverage/is already ignored on line 171, but this wasn't the intended result.Fix it with Roo Code or mention @roomote and request a fix.