$ -weight: 500;">npm -weight: 500;">install @midnight-ntwrk/midnight-js-indexer-public-data-provider \ @midnight-ntwrk/compact-runtime \ graphql-ws
-weight: 500;">npm -weight: 500;">install @midnight-ntwrk/midnight-js-indexer-public-data-provider \ @midnight-ntwrk/compact-runtime \ graphql-ws
-weight: 500;">npm -weight: 500;">install @midnight-ntwrk/midnight-js-indexer-public-data-provider \ @midnight-ntwrk/compact-runtime \ graphql-ws
// src/config.ts
export const INDEXER_HTTP_URL = process.env.REACT_APP_INDEXER_URL ?? 'http://localhost:8088/api/v4/graphql'; export const INDEXER_WS_URL = process.env.REACT_APP_INDEXER_WS_URL ?? 'ws://localhost:8088/api/v4/graphql/ws';
// src/config.ts
export const INDEXER_HTTP_URL = process.env.REACT_APP_INDEXER_URL ?? 'http://localhost:8088/api/v4/graphql'; export const INDEXER_WS_URL = process.env.REACT_APP_INDEXER_WS_URL ?? 'ws://localhost:8088/api/v4/graphql/ws';
// src/config.ts
export const INDEXER_HTTP_URL = process.env.REACT_APP_INDEXER_URL ?? 'http://localhost:8088/api/v4/graphql'; export const INDEXER_WS_URL = process.env.REACT_APP_INDEXER_WS_URL ?? 'ws://localhost:8088/api/v4/graphql/ws';
import { createIndexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { INDEXER_WS_URL } from './config'; const publicDataProvider = createIndexerPublicDataProvider(INDEXER_WS_URL);
import { createIndexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { INDEXER_WS_URL } from './config'; const publicDataProvider = createIndexerPublicDataProvider(INDEXER_WS_URL);
import { createIndexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
import { INDEXER_WS_URL } from './config'; const publicDataProvider = createIndexerPublicDataProvider(INDEXER_WS_URL);
import { Counter } from './contract/managed/counter/contract/index.cjs'; const providers: MidnightProviders<Counter.Ledger> = { publicDataProvider, // ...wallet, proofProvider, privateStateProvider
}; const contractAPI = Counter.createContractAPI(contractAddress, providers);
import { Counter } from './contract/managed/counter/contract/index.cjs'; const providers: MidnightProviders<Counter.Ledger> = { publicDataProvider, // ...wallet, proofProvider, privateStateProvider
}; const contractAPI = Counter.createContractAPI(contractAddress, providers);
import { Counter } from './contract/managed/counter/contract/index.cjs'; const providers: MidnightProviders<Counter.Ledger> = { publicDataProvider, // ...wallet, proofProvider, privateStateProvider
}; const contractAPI = Counter.createContractAPI(contractAddress, providers);
import { useEffect, useState } from 'react'; function useCounterState(contractAPI: ReturnType<typeof Counter.createContractAPI>) { const [state, setState] = useState<Counter.Ledger.State | null>(null); useEffect(() => { const sub = contractAPI.state.subscribe({ next: setState, error: console.error, }); return () => sub.unsubscribe(); }, [contractAPI]); return state;
}
import { useEffect, useState } from 'react'; function useCounterState(contractAPI: ReturnType<typeof Counter.createContractAPI>) { const [state, setState] = useState<Counter.Ledger.State | null>(null); useEffect(() => { const sub = contractAPI.state.subscribe({ next: setState, error: console.error, }); return () => sub.unsubscribe(); }, [contractAPI]); return state;
}
import { useEffect, useState } from 'react'; function useCounterState(contractAPI: ReturnType<typeof Counter.createContractAPI>) { const [state, setState] = useState<Counter.Ledger.State | null>(null); useEffect(() => { const sub = contractAPI.state.subscribe({ next: setState, error: console.error, }); return () => sub.unsubscribe(); }, [contractAPI]); return state;
}
query GetContractState($address: HexEncoded!) { contractAction(address: $address) { __typename ... on ContractDeploy { address state zswapState } ... on ContractCall { address state zswapState entryPoint unshieldedBalances { tokenType amount } } ... on ContractUpdate { address state zswapState } }
}
query GetContractState($address: HexEncoded!) { contractAction(address: $address) { __typename ... on ContractDeploy { address state zswapState } ... on ContractCall { address state zswapState entryPoint unshieldedBalances { tokenType amount } } ... on ContractUpdate { address state zswapState } }
}
query GetContractState($address: HexEncoded!) { contractAction(address: $address) { __typename ... on ContractDeploy { address state zswapState } ... on ContractCall { address state zswapState entryPoint unshieldedBalances { tokenType amount } } ... on ContractUpdate { address state zswapState } }
}
async function fetchContractState(address: string): Promise<string> { const query = ` query GetContractState($address: HexEncoded!) { contractAction(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `; const res = await fetch(INDEXER_HTTP_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { address } }), }); const { data } = await res.json(); return data.contractAction.state as string; // still hex at this point
}
async function fetchContractState(address: string): Promise<string> { const query = ` query GetContractState($address: HexEncoded!) { contractAction(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `; const res = await fetch(INDEXER_HTTP_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { address } }), }); const { data } = await res.json(); return data.contractAction.state as string; // still hex at this point
}
async function fetchContractState(address: string): Promise<string> { const query = ` query GetContractState($address: HexEncoded!) { contractAction(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `; const res = await fetch(INDEXER_HTTP_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { address } }), }); const { data } = await res.json(); return data.contractAction.state as string; // still hex at this point
}
subscription WatchContractActions($address: HexEncoded!) { contractActions(address: $address) { __typename ... on ContractCall { address state entryPoint unshieldedBalances { tokenType amount } } ... on ContractDeploy { address state } ... on ContractUpdate { address state } }
}
subscription WatchContractActions($address: HexEncoded!) { contractActions(address: $address) { __typename ... on ContractCall { address state entryPoint unshieldedBalances { tokenType amount } } ... on ContractDeploy { address state } ... on ContractUpdate { address state } }
}
subscription WatchContractActions($address: HexEncoded!) { contractActions(address: $address) { __typename ... on ContractCall { address state entryPoint unshieldedBalances { tokenType amount } } ... on ContractDeploy { address state } ... on ContractUpdate { address state } }
}
subscription WatchFromBlock($address: HexEncoded!, $height: Int!) { contractActions(address: $address, offset: { height: $height }) { ... on ContractCall { state entryPoint } }
}
subscription WatchFromBlock($address: HexEncoded!, $height: Int!) { contractActions(address: $address, offset: { height: $height }) { ... on ContractCall { state entryPoint } }
}
subscription WatchFromBlock($address: HexEncoded!, $height: Int!) { contractActions(address: $address, offset: { height: $height }) { ... on ContractCall { state entryPoint } }
}
async function setupStateStream( address: string, onState: (hexState: string) => void
) { // Get current state immediately const initial = await fetchContractState(address); onState(initial); // Then subscribe for future updates const client = createClient({ url: INDEXER_WS_URL }); const unsub = client.subscribe( { query: ` subscription ($address: HexEncoded!) { contractActions(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `, variables: { address }, }, { next: ({ data }) => onState(data.contractActions.state), error: console.error, complete: () => console.log('subscription closed'), } ); return unsub; // call this to cancel
}
async function setupStateStream( address: string, onState: (hexState: string) => void
) { // Get current state immediately const initial = await fetchContractState(address); onState(initial); // Then subscribe for future updates const client = createClient({ url: INDEXER_WS_URL }); const unsub = client.subscribe( { query: ` subscription ($address: HexEncoded!) { contractActions(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `, variables: { address }, }, { next: ({ data }) => onState(data.contractActions.state), error: console.error, complete: () => console.log('subscription closed'), } ); return unsub; // call this to cancel
}
async function setupStateStream( address: string, onState: (hexState: string) => void
) { // Get current state immediately const initial = await fetchContractState(address); onState(initial); // Then subscribe for future updates const client = createClient({ url: INDEXER_WS_URL }); const unsub = client.subscribe( { query: ` subscription ($address: HexEncoded!) { contractActions(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `, variables: { address }, }, { next: ({ data }) => onState(data.contractActions.state), error: console.error, complete: () => console.log('subscription closed'), } ); return unsub; // call this to cancel
}
import { createClient } from 'graphql-ws';
import { createClient } from 'graphql-ws';
import { createClient } from 'graphql-ws';
{ "data": { "contractActions": { "state": "000000000000000a0000000000000000" } }
}
{ "data": { "contractActions": { "state": "000000000000000a0000000000000000" } }
}
{ "data": { "contractActions": { "state": "000000000000000a0000000000000000" } }
}
import { Counter } from './contract/managed/counter/contract/index.cjs'; function hexToBytes(hex: string): Uint8Array { const clean = hex.startsWith('0x') ? hex.slice(2) : hex; const bytes = new Uint8Array(clean.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); } return bytes;
} function decodeState(hexState: string): Counter.Ledger.State { const bytes = hexToBytes(hexState); return Counter.Ledger.State.deserialize(bytes);
}
import { Counter } from './contract/managed/counter/contract/index.cjs'; function hexToBytes(hex: string): Uint8Array { const clean = hex.startsWith('0x') ? hex.slice(2) : hex; const bytes = new Uint8Array(clean.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); } return bytes;
} function decodeState(hexState: string): Counter.Ledger.State { const bytes = hexToBytes(hexState); return Counter.Ledger.State.deserialize(bytes);
}
import { Counter } from './contract/managed/counter/contract/index.cjs'; function hexToBytes(hex: string): Uint8Array { const clean = hex.startsWith('0x') ? hex.slice(2) : hex; const bytes = new Uint8Array(clean.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); } return bytes;
} function decodeState(hexState: string): Counter.Ledger.State { const bytes = hexToBytes(hexState); return Counter.Ledger.State.deserialize(bytes);
}
// Generated by Compact compiler — don't edit
namespace Ledger { export type State = { count: bigint; owner: Uint8Array; // public key bytes }; export const State = { serialize: (state: State): Uint8Array => { /* ... */ }, deserialize: (bytes: Uint8Array): State => { /* ... */ }, };
}
// Generated by Compact compiler — don't edit
namespace Ledger { export type State = { count: bigint; owner: Uint8Array; // public key bytes }; export const State = { serialize: (state: State): Uint8Array => { /* ... */ }, deserialize: (bytes: Uint8Array): State => { /* ... */ }, };
}
// Generated by Compact compiler — don't edit
namespace Ledger { export type State = { count: bigint; owner: Uint8Array; // public key bytes }; export const State = { serialize: (state: State): Uint8Array => { /* ... */ }, deserialize: (bytes: Uint8Array): State => { /* ... */ }, };
}
// Safe for display
<span>{state.count.toString()}</span> // Safe math
const next = state.count + 1n; // note the 'n' suffix // Unsafe — don't do this for contract values
const display = Number(state.count); // corrupts above 2^53
// Safe for display
<span>{state.count.toString()}</span> // Safe math
const next = state.count + 1n; // note the 'n' suffix // Unsafe — don't do this for contract values
const display = Number(state.count); // corrupts above 2^53
// Safe for display
<span>{state.count.toString()}</span> // Safe math
const next = state.count + 1n; // note the 'n' suffix // Unsafe — don't do this for contract values
const display = Number(state.count); // corrupts above 2^53
import { useEffect, useRef, useState } from 'react';
import { createClient } from 'graphql-ws';
import { Counter } from './contract/managed/counter/contract/index.cjs';
import { INDEXER_HTTP_URL, INDEXER_WS_URL } from './config'; type ContractState = Counter.Ledger.State; function hexToBytes(hex: string): Uint8Array { const clean = hex.startsWith('0x') ? hex.slice(2) : hex; const bytes = new Uint8Array(clean.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); } return bytes;
} const CONTRACT_STATE_SUBSCRIPTION = ` subscription WatchContract($address: HexEncoded!) { contractActions(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } }
`; async function fetchCurrentState(address: string): Promise<ContractState> { const res = await fetch(INDEXER_HTTP_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ` query ($address: HexEncoded!) { contractAction(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `, variables: { address }, }), }); const { data } = await res.json(); return Counter.Ledger.State.deserialize(hexToBytes(data.contractAction.state));
} export function useContractState(contractAddress: string) { const [state, setState] = useState<ContractState | null>(null); const [error, setError] = useState<Error | null>(null); const [loading, setLoading] = useState(true); const clientRef = useRef<ReturnType<typeof createClient> | null>(null); useEffect(() => { if (!contractAddress) return; let cancelled = false; // Fetch current state first fetchCurrentState(contractAddress) .then((s) => { if (!cancelled) { setState(s); setLoading(false); } }) .catch((e) => { if (!cancelled) { setError(e); setLoading(false); } }); // Then subscribe for updates const client = createClient({ url: INDEXER_WS_URL }); clientRef.current = client; const unsub = client.subscribe( { query: CONTRACT_STATE_SUBSCRIPTION, variables: { address: contractAddress }, }, { next: ({ data }) => { if (!cancelled) { const hexState = data?.contractActions?.state; if (hexState) { try { setState(Counter.Ledger.State.deserialize(hexToBytes(hexState))); } catch (e) { setError(e as Error); } } } }, error: (e) => { if (!cancelled) setError(e as Error); }, complete: () => {}, } ); return () => { cancelled = true; unsub(); client.dispose(); }; }, [contractAddress]); return { state, loading, error };
}
import { useEffect, useRef, useState } from 'react';
import { createClient } from 'graphql-ws';
import { Counter } from './contract/managed/counter/contract/index.cjs';
import { INDEXER_HTTP_URL, INDEXER_WS_URL } from './config'; type ContractState = Counter.Ledger.State; function hexToBytes(hex: string): Uint8Array { const clean = hex.startsWith('0x') ? hex.slice(2) : hex; const bytes = new Uint8Array(clean.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); } return bytes;
} const CONTRACT_STATE_SUBSCRIPTION = ` subscription WatchContract($address: HexEncoded!) { contractActions(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } }
`; async function fetchCurrentState(address: string): Promise<ContractState> { const res = await fetch(INDEXER_HTTP_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ` query ($address: HexEncoded!) { contractAction(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `, variables: { address }, }), }); const { data } = await res.json(); return Counter.Ledger.State.deserialize(hexToBytes(data.contractAction.state));
} export function useContractState(contractAddress: string) { const [state, setState] = useState<ContractState | null>(null); const [error, setError] = useState<Error | null>(null); const [loading, setLoading] = useState(true); const clientRef = useRef<ReturnType<typeof createClient> | null>(null); useEffect(() => { if (!contractAddress) return; let cancelled = false; // Fetch current state first fetchCurrentState(contractAddress) .then((s) => { if (!cancelled) { setState(s); setLoading(false); } }) .catch((e) => { if (!cancelled) { setError(e); setLoading(false); } }); // Then subscribe for updates const client = createClient({ url: INDEXER_WS_URL }); clientRef.current = client; const unsub = client.subscribe( { query: CONTRACT_STATE_SUBSCRIPTION, variables: { address: contractAddress }, }, { next: ({ data }) => { if (!cancelled) { const hexState = data?.contractActions?.state; if (hexState) { try { setState(Counter.Ledger.State.deserialize(hexToBytes(hexState))); } catch (e) { setError(e as Error); } } } }, error: (e) => { if (!cancelled) setError(e as Error); }, complete: () => {}, } ); return () => { cancelled = true; unsub(); client.dispose(); }; }, [contractAddress]); return { state, loading, error };
}
import { useEffect, useRef, useState } from 'react';
import { createClient } from 'graphql-ws';
import { Counter } from './contract/managed/counter/contract/index.cjs';
import { INDEXER_HTTP_URL, INDEXER_WS_URL } from './config'; type ContractState = Counter.Ledger.State; function hexToBytes(hex: string): Uint8Array { const clean = hex.startsWith('0x') ? hex.slice(2) : hex; const bytes = new Uint8Array(clean.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16); } return bytes;
} const CONTRACT_STATE_SUBSCRIPTION = ` subscription WatchContract($address: HexEncoded!) { contractActions(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } }
`; async function fetchCurrentState(address: string): Promise<ContractState> { const res = await fetch(INDEXER_HTTP_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ` query ($address: HexEncoded!) { contractAction(address: $address) { ... on ContractCall { state } ... on ContractDeploy { state } ... on ContractUpdate { state } } } `, variables: { address }, }), }); const { data } = await res.json(); return Counter.Ledger.State.deserialize(hexToBytes(data.contractAction.state));
} export function useContractState(contractAddress: string) { const [state, setState] = useState<ContractState | null>(null); const [error, setError] = useState<Error | null>(null); const [loading, setLoading] = useState(true); const clientRef = useRef<ReturnType<typeof createClient> | null>(null); useEffect(() => { if (!contractAddress) return; let cancelled = false; // Fetch current state first fetchCurrentState(contractAddress) .then((s) => { if (!cancelled) { setState(s); setLoading(false); } }) .catch((e) => { if (!cancelled) { setError(e); setLoading(false); } }); // Then subscribe for updates const client = createClient({ url: INDEXER_WS_URL }); clientRef.current = client; const unsub = client.subscribe( { query: CONTRACT_STATE_SUBSCRIPTION, variables: { address: contractAddress }, }, { next: ({ data }) => { if (!cancelled) { const hexState = data?.contractActions?.state; if (hexState) { try { setState(Counter.Ledger.State.deserialize(hexToBytes(hexState))); } catch (e) { setError(e as Error); } } } }, error: (e) => { if (!cancelled) setError(e as Error); }, complete: () => {}, } ); return () => { cancelled = true; unsub(); client.dispose(); }; }, [contractAddress]); return { state, loading, error };
}
import React from 'react';
import { useContractState } from './useContractState'; interface CounterDisplayProps { contractAddress: string;
} export function CounterDisplay({ contractAddress }: CounterDisplayProps) { const { state, loading, error } = useContractState(contractAddress); if (loading) return <div className="counter">Loading...</div>; if (error) return <div className="counter error">Error: {error.message}</div>; if (!state) return null; return ( <div className="counter"> <h2>Counter</h2> <p className="count">{state.count.toString()}</p> <p className="owner"> Owner: {Buffer.from(state.owner).toString('hex').slice(0, 16)}... </p> </div> );
}
import React from 'react';
import { useContractState } from './useContractState'; interface CounterDisplayProps { contractAddress: string;
} export function CounterDisplay({ contractAddress }: CounterDisplayProps) { const { state, loading, error } = useContractState(contractAddress); if (loading) return <div className="counter">Loading...</div>; if (error) return <div className="counter error">Error: {error.message}</div>; if (!state) return null; return ( <div className="counter"> <h2>Counter</h2> <p className="count">{state.count.toString()}</p> <p className="owner"> Owner: {Buffer.from(state.owner).toString('hex').slice(0, 16)}... </p> </div> );
}
import React from 'react';
import { useContractState } from './useContractState'; interface CounterDisplayProps { contractAddress: string;
} export function CounterDisplay({ contractAddress }: CounterDisplayProps) { const { state, loading, error } = useContractState(contractAddress); if (loading) return <div className="counter">Loading...</div>; if (error) return <div className="counter error">Error: {error.message}</div>; if (!state) return null; return ( <div className="counter"> <h2>Counter</h2> <p className="count">{state.count.toString()}</p> <p className="owner"> Owner: {Buffer.from(state.owner).toString('hex').slice(0, 16)}... </p> </div> );
}
query { contractAction(address: "YOUR_CONTRACT_ADDRESS_HERE") { __typename ... on ContractCall { state entryPoint } ... on ContractDeploy { state } }
}
query { contractAction(address: "YOUR_CONTRACT_ADDRESS_HERE") { __typename ... on ContractCall { state entryPoint } ... on ContractDeploy { state } }
}
query { contractAction(address: "YOUR_CONTRACT_ADDRESS_HERE") { __typename ... on ContractCall { state entryPoint } ... on ContractDeploy { state } }
} - HTTP: http://<host>:<port>/api/v4/graphql — for one-shot queries
- WebSocket: wss://<host>:<port>/api/v4/graphql/ws — for subscriptions