
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@zktx.io/ptb-builder
Advanced tools
PTB Builder is a graphical toolkit for building, simulating, and executing Programmable Transaction Blocks (PTBs) on the Sui blockchain. It provides an intuitive drag‑and‑drop interface, automatic code generation, and on‑chain execution support — bridging the gap between developers and non‑developers.

TransferObjects, MergeCoins, MoveCall.dark, light, cobalt2, tokyo night, cream, mint.breeze.The following PTB commands are currently supported:
(Additional commands can be added via registry extensions.)
Inputs follow tx.option conventions from the Sui TS SDK:
Coin<T>)
Below snippets mirror a typical setup using @mysten/dapp‑kit with PTB Builder.
App.tsximport { PTBBuilder, Chain, ToastVariant } from '@zktx.io/ptb-builder';
import { Transaction } from '@mysten/sui/transactions';
import {
useCurrentAccount,
useSignAndExecuteTransaction,
} from '@mysten/dapp-kit';
import { enqueueSnackbar } from 'notistack';
import '@mysten/dapp-kit/dist/index.css';
import '@zktx.io/ptb-builder/index.css';
function App() {
const account = useCurrentAccount();
const { mutate: signAndExecuteTransaction } = useSignAndExecuteTransaction();
// Toast adapter
const handleToast = ({
message,
variant,
}: {
message: string;
variant?: ToastVariant;
}) => {
enqueueSnackbar(message, { variant });
};
// Execute adapter
const executeTx = async (
chain: Chain,
transaction: Transaction | undefined,
): Promise<{ digest?: string; error?: string }> => {
if (!account || !transaction) return { error: 'No account or transaction' };
try {
const jsonTx = await transaction.toJSON();
return new Promise((resolve) => {
signAndExecuteTransaction(
{ transaction: jsonTx, chain },
{
onSuccess: (result) => resolve({ digest: result.digest }),
onError: (error) => resolve({ error: error.message }),
},
);
});
} catch (e: any) {
return { error: e.message || 'Serialization failed' };
}
};
return (
<PTBBuilder
toast={handleToast}
executeTx={executeTx}
address={account?.address}
showExportButton
/>
);
}
export default App;
pages/editor.tsximport { useCurrentAccount, useSuiClientContext } from '@mysten/dapp-kit';
import { PTB_VERSION, PTBDoc, usePTB } from '@zktx.io/ptb-builder';
import { DragAndDrop } from '../components/DragAndDrop';
type SuiNetwork = 'mainnet' | 'testnet' | 'devnet';
type SuiChain = `sui:${SuiNetwork}`;
export const Editor = () => {
const { network, selectNetwork } = useSuiClientContext();
const account = useCurrentAccount();
const { loadFromDoc } = usePTB();
// Safe parser for "sui:<network>"
const parseNetwork = (chain?: string): SuiNetwork | undefined => {
const m = chain?.match(/^sui:(mainnet|testnet|devnet)$/);
return m?.[1] as SuiNetwork | undefined;
};
const handleDrop = (file: PTBDoc) => {
// Align dapp-kit network only if valid and different
const target = parseNetwork(file.chain);
if (target && target !== network) selectNetwork(target);
loadFromDoc(file);
};
const handleChancel = () => {
// Reset with a current network
loadFromDoc(`sui:${network}` as SuiChain);
};
return (
<div style={{ width: '100vw', height: '100vh' }}>
{account && <DragAndDrop onDrop={handleDrop} onChancel={handleChancel} />}
</div>
);
};
pages/viewer.tsximport { useEffect, useRef, useState } from 'react';
import { usePTB } from '@zktx.io/ptb-builder';
import queryString from 'query-string';
import { useLocation } from 'react-router-dom';
export const Viewer = () => {
const initialized = useRef<boolean>(false);
const { loadFromOnChainTx } = usePTB();
const location = useLocation();
const [txHash, setTxHash] = useState<string | undefined>(undefined);
useEffect(() => {
const parsed = queryString.parse(location.search);
if (parsed.tx && !initialized.current) {
loadFromOnChainTx('sui:testnet', parsed.tx as string);
initialized.current = true;
} else {
setTxHash('');
}
}, [loadFromOnChainTx, location.search, txHash]);
return null;
};
import '@zktx.io/ptb-builder/index.css';
import '@zktx.io/ptb-builder/styles/themes-all.css';
// Or import a specific theme only: import '@zktx.io/ptb-builder/styles/theme-light.css';
import { PTBBuilder } from '@zktx.io/ptb-builder';
<PTBBuilder
theme="dark" // initial theme (dark | light | cobalt2 | "tokyo night" | cream | mint.breeze); defaults to "dark"
address={myAddress} // sender address
gasBudget={500_000_000} // optional gas budget
executeTx={execAdapter} // adapter to execute transactions
onDocChange={saveDoc} // PTBDoc autosave callback (debounced)
showExportButton // optional: show Export .ptb button (default: hidden)
>
{/* your app here */}
</PTBBuilder>;
import { usePTB } from '@zktx.io/ptb-builder';
const { exportDoc, loadFromDoc, loadFromOnChainTx, setTheme } = usePTB();
// Export current PTB document
const doc = exportDoc({ sender: myAddress });
// Load document from memory or disk
loadFromDoc(doc);
// Load graph from on-chain transaction digest
await loadFromOnChainTx('sui:testnet', '0x1234…');
// Switch theme at runtime
setTheme('tokyo night');
@zktx.io/ptb-builder/index.css contains the structural styles for nodes, edges, and the builder chrome. It should be imported exactly once in your host app (or exposed by your bundler) regardless of the theme you choose.@zktx.io/ptb-builder/styles/themes-all.css bundles every theme token file so you can switch themes at runtime with setTheme. Pulling in the whole pack adds roughly ~18 kB pre-gzip.import '@zktx.io/ptb-builder/styles/theme-dark.css';. Each theme file is ~3 kB pre-gzip, so picking a single one keeps the bundle lean while still allowing dynamic switching between the themes you explicitly include.theme value (e.g., theme="light") and set showThemeSelector={false} so the UI doesn’t expose choices that aren’t bundled.onDocChangeonDocChange immediately when the underlying PTB graph, modules, objects, or active chain changes. Viewport changes (pan/zoom) are debounced by 250 ms so autosave targets are not overwhelmed while the user drags the canvas.loadFromDoc/loadFromOnChainTx resets the internal history cache, replays the snapshot once, and suppresses duplicate events until the user edits again.usePtbUndo hook keeps a stable signature per PTBDoc, so undo/redo operations call loadFromDoc without collapsing the redo stack. A single flag (suppressNext) prevents the ensuing onDocChange from being treated as a fresh edit.onDocChange to fire often during graph edits but only after the debounce window for viewport-only motions.<PTBBuilder />)| Prop | Type | Default | Description |
|---|---|---|---|
theme | Theme (dark | light | cobalt2 | tokyo night | cream | mint.breeze) | "dark" | Initial UI theme. |
showThemeSelector | boolean | true | Renders the theme dropdown in the CodePip panel. |
address | string | – | Sender address for generated transactions. |
gasBudget | number | – | Optional gas budget used for tx build/exec. |
executeTx | (chain: Chain, tx?: Transaction) => Promise<{ digest?: string; error?: string }> | – | Adapter to execute transactions. |
toast | ToastAdapter | console | Custom toast adapter used by the provider. |
onDocChange | (doc: PTBDoc) => void | – | Autosave callback (debounced). |
showExportButton | boolean | false | If true, shows Export .ptb button in the CodePip panel. |
children | React.ReactNode | – | Children rendered inside the Provider. |
PTB Builder persists graphs as PTBDoc objects containing:
sui:testnet)This enables saving, sharing, and reloading graphs consistently across environments.
useSuiClientContext() from @mysten/dapp‑kit to read/change the active Sui network.doc.chain in the form sui:<network>, e.g., sui:testnet./^sui:(mainnet|testnet|devnet)$/ before switching the network.FAQs
Sui programmable transaction blocks builder
The npm package @zktx.io/ptb-builder receives a total of 80 weekly downloads. As such, @zktx.io/ptb-builder popularity was classified as not popular.
We found that @zktx.io/ptb-builder demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.