-
+
-
diff --git a/examples/ore-react/src/components/index.ts b/examples/ore-react/src/components/index.ts
index 91858a2..9e7778f 100644
--- a/examples/ore-react/src/components/index.ts
+++ b/examples/ore-react/src/components/index.ts
@@ -2,4 +2,5 @@ export { OreDashboard } from './OreDashboard';
export { BlockGrid } from './BlockGrid';
export { StatsPanel } from './StatsPanel';
export { ConnectionBadge } from './ConnectionBadge';
+export { DeployButton } from './DeployButton';
export * from './icons';
diff --git a/examples/ore-react/vite.config.ts b/examples/ore-react/vite.config.ts
index 9ffcc67..956cd04 100644
--- a/examples/ore-react/vite.config.ts
+++ b/examples/ore-react/vite.config.ts
@@ -1,6 +1,20 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import { nodePolyfills } from 'vite-plugin-node-polyfills'
export default defineConfig({
- plugins: [react()],
+ plugins: [
+ react(),
+ nodePolyfills({
+ include: ['buffer', 'process'],
+ globals: {
+ Buffer: true,
+ process: true,
+ },
+ }),
+ ],
+ optimizeDeps: {
+ // exclude local hyperstack packages from pre-bundling
+ exclude: ['hyperstack-typescript', 'hyperstack-react', 'hyperstack-stacks'],
+ },
})
diff --git a/stacks/sdk/typescript/src/ore/index.ts b/stacks/sdk/typescript/src/ore/index.ts
index 46975ec..09ad110 100644
--- a/stacks/sdk/typescript/src/ore/index.ts
+++ b/stacks/sdk/typescript/src/ore/index.ts
@@ -1,4 +1,10 @@
import { z } from 'zod';
+import type {
+ InstructionHandler,
+ AccountMeta,
+ ArgSchema
+} from 'hyperstack-typescript';
+import { serializeInstructionData } from 'hyperstack-typescript';
export interface OreRoundEntropy {
entropy_end_at?: number | null;
@@ -357,6 +363,170 @@ export const OreMinerCompletedSchema = z.object({
automation_snapshot: AutomationSchema,
});
+// ============================================================================
+// Instruction Handlers
+// ============================================================================
+
+const ORE_PROGRAM_ID = "oreV3EG1i9BEgiAJ8b177Z2S2rMarzak4NMv1kULvWv";
+const ENTROPY_PROGRAM_ID = "3jSkUuYBoJzQPMEzTvkDFXCZUBksPamrVhrnHR9igu2X";
+const SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
+const DEPLOY_DISCRIMINATOR = new Uint8Array([6]);
+const CHECKPOINT_DISCRIMINATOR = new Uint8Array([2]);
+
+/*
+* Deploy Instruction
+*/
+const deployAccounts: AccountMeta[] = [
+ { name: "signer", category: "signer", isSigner: true, isWritable: true },
+ { name: "authority", category: "signer", isSigner: false, isWritable: true },
+ {
+ name: "automation",
+ category: "pda",
+ pdaConfig: {
+ programId: ORE_PROGRAM_ID,
+ seeds: [
+ { type: "literal", value: "automation" },
+ { type: "accountRef", accountName: "authority" },
+ ],
+ },
+ isSigner: false,
+ isWritable: true,
+ },
+ {
+ name: "board",
+ category: "pda",
+ pdaConfig: {
+ programId: ORE_PROGRAM_ID,
+ seeds: [{ type: "literal", value: "board" }],
+ },
+ isSigner: false,
+ isWritable: true,
+ },
+ {
+ name: "config",
+ category: "pda",
+ pdaConfig: {
+ programId: ORE_PROGRAM_ID,
+ seeds: [{ type: "literal", value: "config" }],
+ },
+ isSigner: false,
+ isWritable: true,
+ },
+ {
+ name: "miner",
+ category: "pda",
+ pdaConfig: {
+ programId: ORE_PROGRAM_ID,
+ seeds: [
+ { type: "literal", value: "miner" },
+ { type: "accountRef", accountName: "authority" },
+ ],
+ },
+ isSigner: false,
+ isWritable: true,
+ },
+ { name: "round", category: "userProvided", isSigner: false, isWritable: true },
+ { name: "systemProgram", category: "known", knownAddress: SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false },
+ { name: "oreProgram", category: "known", knownAddress: ORE_PROGRAM_ID, isSigner: false, isWritable: false },
+ { name: "entropyVar", category: "userProvided", isSigner: false, isWritable: true },
+ { name: "entropyProgram", category: "known", knownAddress: ENTROPY_PROGRAM_ID, isSigner: false, isWritable: false },
+];
+
+const deployArgsSchema: ArgSchema[] = [
+ { name: "amount", type: "u64" },
+ { name: "squares", type: "u32" },
+];
+
+const deployInstruction: InstructionHandler = {
+ programId: ORE_PROGRAM_ID,
+ accounts: deployAccounts,
+ errors: [
+ { code: 0, name: "AmountTooSmall", msg: "Amount too small" },
+ { code: 1, name: "NotAuthorized", msg: "Not authorized" },
+ ],
+ build: (args, resolvedAccounts) => {
+ if (typeof args.amount !== 'bigint' && typeof args.amount !== 'number') {
+ throw new Error('amount must be a number or bigint');
+ }
+ if (typeof args.squares !== 'number') {
+ throw new Error('squares must be a number');
+ }
+
+ const buffer = serializeInstructionData(DEPLOY_DISCRIMINATOR, args, deployArgsSchema);
+ const data = new Uint8Array(buffer);
+
+ const keys = deployAccounts.map(meta => ({
+ pubkey: resolvedAccounts[meta.name]!,
+ isSigner: meta.isSigner,
+ isWritable: meta.isWritable,
+ }));
+
+ return { programId: ORE_PROGRAM_ID, keys, data };
+ },
+};
+
+/*
+* Checkpoint Instruction
+*/
+const checkpointAccounts: AccountMeta[] = [
+ { name: "signer", category: "signer", isSigner: true, isWritable: true },
+ {
+ name: "board",
+ category: "pda",
+ pdaConfig: {
+ programId: ORE_PROGRAM_ID,
+ seeds: [{ type: "literal", value: "board" }],
+ },
+ isSigner: false,
+ isWritable: false,
+ },
+ {
+ name: "miner",
+ category: "pda",
+ pdaConfig: {
+ programId: ORE_PROGRAM_ID,
+ seeds: [
+ { type: "literal", value: "miner" },
+ { type: "accountRef", accountName: "signer" },
+ ],
+ },
+ isSigner: false,
+ isWritable: true,
+ },
+ { name: "round", category: "userProvided", isSigner: false, isWritable: true },
+ {
+ name: "treasury",
+ category: "pda",
+ pdaConfig: {
+ programId: ORE_PROGRAM_ID,
+ seeds: [{ type: "literal", value: "treasury" }],
+ },
+ isSigner: false,
+ isWritable: true,
+ },
+ { name: "systemProgram", category: "known", knownAddress: SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false },
+];
+
+const checkpointInstruction: InstructionHandler = {
+ programId: ORE_PROGRAM_ID,
+ accounts: checkpointAccounts,
+ errors: [
+ { code: 0, name: "AmountTooSmall", msg: "Amount too small" },
+ { code: 1, name: "NotAuthorized", msg: "Not authorized" },
+ ],
+ build: (args, resolvedAccounts) => {
+ const data = CHECKPOINT_DISCRIMINATOR;
+
+ const keys = checkpointAccounts.map(meta => ({
+ pubkey: resolvedAccounts[meta.name]!,
+ isSigner: meta.isSigner,
+ isWritable: meta.isWritable,
+ }));
+
+ return { programId: ORE_PROGRAM_ID, keys, data };
+ },
+};
+
// ============================================================================
// View Definition Types (framework-agnostic)
// ============================================================================
@@ -402,6 +572,10 @@ export const ORE_STREAM_STACK = {
list: listView
('OreMiner/list'),
},
},
+ instructions: {
+ checkpoint: checkpointInstruction,
+ deploy: deployInstruction,
+ } as const,
schemas: {
Automation: AutomationSchema,
Miner: MinerSchema,
diff --git a/typescript/core/package-lock.json b/typescript/core/package-lock.json
index fdfc723..50590d5 100644
--- a/typescript/core/package-lock.json
+++ b/typescript/core/package-lock.json
@@ -9,6 +9,8 @@
"version": "0.5.2",
"license": "MIT",
"dependencies": {
+ "@noble/curves": "^2.0.1",
+ "@noble/hashes": "^2.0.1",
"pako": "^2.1.0",
"zod": "^3.24.1"
},
@@ -616,6 +618,33 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@noble/curves": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz",
+ "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
+ "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
diff --git a/typescript/core/package.json b/typescript/core/package.json
index 97bcd1d..238e7e0 100644
--- a/typescript/core/package.json
+++ b/typescript/core/package.json
@@ -47,13 +47,15 @@
"node": ">=16.0.0"
},
"dependencies": {
+ "@noble/curves": "^2.0.1",
+ "@noble/hashes": "^2.0.1",
"pako": "^2.1.0",
"zod": "^3.24.1"
},
"devDependencies": {
- "@types/pako": "^2.0.3",
"@rollup/plugin-typescript": "^11.0.0",
"@types/node": "^20.0.0",
+ "@types/pako": "^2.0.3",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.0.0",
diff --git a/typescript/core/src/instructions/pda.ts b/typescript/core/src/instructions/pda.ts
index ff4f81d..c947f3a 100644
--- a/typescript/core/src/instructions/pda.ts
+++ b/typescript/core/src/instructions/pda.ts
@@ -4,6 +4,9 @@
* Implements Solana's PDA derivation algorithm without depending on @solana/web3.js.
*/
+import { sha256 } from '@noble/hashes/sha2.js';
+import { ed25519 } from '@noble/curves/ed25519.js';
+
// Base58 alphabet (Bitcoin/Solana style)
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
@@ -77,12 +80,10 @@ export function encodeBase58(bytes: Uint8Array): string {
}
/**
- * SHA-256 hash function (synchronous, Node.js).
+ * SHA-256 hash function (synchronous).
*/
function sha256Sync(data: Uint8Array): Uint8Array {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const { createHash } = require('crypto');
- return new Uint8Array(createHash('sha256').update(Buffer.from(data)).digest());
+ return sha256(data);
}
/**
@@ -100,18 +101,17 @@ async function sha256Async(data: Uint8Array): Promise {
/**
* Check if a point is on the ed25519 curve.
- * A valid PDA must be OFF the curve.
- *
- * This is a simplified implementation.
- * In practice, most PDAs are valid on first try with bump=255.
+ * A valid PDA must be OFF the curve to ensure it has no corresponding private key.
+ * Uses @noble/curves for browser-compatible ed25519 validation.
*/
-function isOnCurve(_publicKey: Uint8Array): boolean {
- // Simplified heuristic: actual curve check requires ed25519 math
- // For Solana PDAs, we try bumps from 255 down
- // The first bump (255) almost always produces a valid off-curve point
- // We return false here to accept the first result
- // In production with @solana/web3.js, use PublicKey.isOnCurve()
- return false;
+function isOnCurve(publicKey: Uint8Array): boolean {
+ try {
+ // Try to decode as an ed25519 point - if successful, it's on the curve
+ ed25519.Point.fromBytes(publicKey);
+ return true; // Point is on curve - invalid for PDA
+ } catch {
+ return false; // Point is off curve - valid for PDA
+ }
}
/**
@@ -200,7 +200,8 @@ export async function findProgramAddress(
const hash = await sha256Async(buffer);
if (!isOnCurve(hash)) {
- return [encodeBase58(hash), bump];
+ const result = encodeBase58(hash);
+ return [result, bump];
}
}
@@ -228,7 +229,8 @@ export function findProgramAddressSync(
const hash = sha256Sync(buffer);
if (!isOnCurve(hash)) {
- return [encodeBase58(hash), bump];
+ const result = encodeBase58(hash);
+ return [result, bump];
}
}
diff --git a/typescript/react/src/stack.ts b/typescript/react/src/stack.ts
index 0d1e996..7a3b95d 100644
--- a/typescript/react/src/stack.ts
+++ b/typescript/react/src/stack.ts
@@ -159,10 +159,20 @@ export function useHyperstack(
useMutation: () => useInstructionMutation(executeFn as InstructionExecutor)
};
}
+ } else if (stack.instructions) {
+ for (const instructionName of Object.keys(stack.instructions)) {
+ const placeholderExecutor = () => {
+ throw new Error(`Cannot execute ${instructionName}: client not connected`);
+ };
+ result[instructionName] = {
+ execute: placeholderExecutor,
+ useMutation: () => useInstructionMutation(placeholderExecutor)
+ };
+ }
}
return result;
- }, [client]);
+ }, [client, stack.instructions]);
return {
views: views as BuildViewInterface,