
Research
/Security News
737 Chrome VPN Extensions Linked to Brand Impersonation and Browser Traffic Redirection
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.
@zktx.io/ptb-builder
Advanced tools
PTB Builder is a graphical toolkit for authoring, inspecting, and rendering Programmable Transaction Blocks (PTBs) on the Sui blockchain. It provides an intuitive drag-and-drop interface, code rendering, and host integration points. The host application remains responsible for wallet connection, signing, simulation, and execution.

@zktx.io/ptb-builder depends on @zktx.io/ptb-model so the builder can adopt
the model package as its canonical PTB data boundary. Model package APIs are the
boundary for PTB data validation, raw PTB conversion, Mermaid rendering, and
TypeScript SDK code string rendering. The builder package owns React UI state,
React Flow integration, document/provider lifecycle, SDK Core read helpers,
object authoring policy, and the runtime adapter that turns a validated
TransactionIR into a host-owned Sui Transaction.
Import the model package through @zktx.io/ptb-model only. Package-internal
model imports, model dist imports, and relative imports across package
boundaries are intentionally blocked for builder source.
The builder runtime accepts ptb_4 documents. Convert unsupported document
shapes outside this package before calling builder/model APIs.
.ptb documents with other PTB Builder users.address prop when a host wallet/address is connected (coins, Move objects, modules, etc.).tx.object(id), for unresolved object ids.dark, light, cobalt2, tokyo-night, cream, mint-breeze.The following command nodes are available from the builder context menu:
Loaded PTBs may also render Publish and Upgrade command nodes for inspection. PTB Builder does not expose context-menu authoring for those commands because editing module bytes, dependencies, and package upgrade data requires the Move toolchain and remains outside the builder UI boundary.
The package does not expose a public command registry extension API.
Input authoring support:
Coin<T>)
address is provided.tx.object(id).Install the builder package plus its peer dependencies in your React app:
npm install @zktx.io/ptb-builder @mysten/sui @xyflow/react elkjs lucide-react re-resizable react react-dom
This package is developed and tested against the exact pinned
@mysten/sui@2.16.2 SDK version used by the repository. Use that SDK version
unless a later PTB Builder release states a different compatibility range.
For authoring, inspection, and TypeScript SDK code preview, the smallest useful
setup is the component, CSS, a starting chain, and a sized container. Passing
initialChain creates a fresh editable PTB document on mount. It is intentionally
an initializer, not a network controller; use loadFromDoc() when your app needs
to replace the active document.
import { PTBBuilder } from '@zktx.io/ptb-builder';
import '@zktx.io/ptb-builder/index.css';
import '@zktx.io/ptb-builder/styles/themes-all.css';
export function App() {
return (
<PTBBuilder
initialChain="sui:testnet"
style={{ width: '100vw', height: '100vh' }}
/>
);
}
That minimal setup does not connect a wallet and does not execute or simulate transactions. It can still author PTB graphs, render code, export documents when enabled, and use the package default SDK Core client for read/load helpers.
Add host integration only for the capabilities your app owns. The next example
assumes your app already wraps this component in the dapp-kit provider setup
shown by the local packages/example app.
import {
useCurrentAccount,
useCurrentNetwork,
useDAppKit,
} from '@mysten/dapp-kit-react';
import type { Transaction } from '@mysten/sui/transactions';
import { PTBBuilder, type Chain } from '@zktx.io/ptb-builder';
function chainToNetwork(chain: Chain) {
const match = chain.match(/^sui:(mainnet|testnet|devnet)$/);
if (!match) throw new Error(`Unsupported chain: ${chain}`);
return match[1] as 'mainnet' | 'testnet' | 'devnet';
}
export function BuilderWithHostAdapters() {
const account = useCurrentAccount();
const network = useCurrentNetwork() ?? 'testnet';
const dAppKit = useDAppKit();
const createClient = (chain: Chain) =>
dAppKit.getClient(chainToNetwork(chain));
const simulateTx = async (chain: Chain, transaction?: Transaction) => {
if (!transaction) return { error: 'No transaction to simulate' };
const client = createClient(chain);
const bytes = await transaction.build({ client });
const result = await client.core.simulateTransaction({
transaction: bytes,
include: { effects: true },
});
const simulated =
result.$kind === 'Transaction'
? result.Transaction
: result.FailedTransaction;
const error =
simulated.status.error?.message || simulated.status.error?.$kind;
return { success: simulated.status.success, error };
};
const executeTx = async (chain: Chain, transaction?: Transaction) => {
if (!account) return { error: 'Wallet not connected' };
if (!transaction) return { error: 'No transaction to execute' };
if (network !== chainToNetwork(chain)) {
return {
error: `Switch to ${chainToNetwork(chain)} before executing this PTB`,
};
}
const result = await dAppKit.signAndExecuteTransaction({ transaction });
if (result.$kind === 'FailedTransaction') {
const statusError = result.FailedTransaction.status.error;
return {
digest: result.FailedTransaction.digest,
error:
statusError?.message ||
statusError?.$kind ||
'Transaction execution failed',
};
}
return { digest: result.Transaction.digest };
};
return (
<PTBBuilder
initialChain={`sui:${network}` as Chain}
style={{ width: '100vw', height: '100vh' }}
createClient={createClient}
simulateTx={simulateTx}
executeTx={executeTx}
address={account?.address}
showExportButton
/>
);
}
The local packages/example app shows a complete dapp-kit host with network
selection, undo/redo, document drop, on-chain transaction loading, and toast
integration. loadFromDoc() accepts ptb_4 documents with explicit chain
and view fields only. Convert unsupported document shapes outside this
package.
createPtbCoreClient() and createPtbCoreClientForNetwork() return SDK Core
clients for read/load paths. The exported PtbCoreClient type is an alias of
the pinned @mysten/sui@2.16.2 ClientWithCoreApi type, not a separate stable
client abstraction owned by PTB Builder. Host applications may provide their own
ClientWithCoreApi-compatible client through createClient, but SDK Core type
changes are part of the Sui SDK boundary and should be reviewed when upgrading
@mysten/sui.
Supported public imports are the package root (@zktx.io/ptb-builder) and the
CSS subpaths declared in package.json exports. Files emitted under
dist/types/ are build artifacts for those exports, not separate compatibility
entry points. Helpers that are not re-exported from the package root are internal
implementation details.
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"
initialChain="sui:testnet" // optional: start with a fresh editable PTB for this chain
address={connectedAddress} // optional runtime sender and Assets modal owner; short or canonical form
gasBudget={500_000_000} // optional runtime gas budget; number, bigint, or u64 string
executeTx={execAdapter} // host-provided execution adapter
createClient={clientFactory} // host-provided SDK Core client factory for reads/loads
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 {
captureCurrentDocResult,
exportDoc,
exportDocResult,
loadFromDoc,
loadFromOnChainTx,
undo,
redo,
canUndo,
canRedo,
setTheme,
} = usePTB();
// Export the active PTB document with structured error information
const exportResult = exportDocResult({ sender: connectedAddress });
if (!exportResult.ok) {
console.warn(exportResult.error);
}
// Compatibility wrapper: returns undefined on failure
const doc = exportDoc({ sender: connectedAddress });
// Load document from memory or disk
if (doc) {
const loadResult = loadFromDoc(doc);
if (!loadResult.ok) {
console.warn(loadResult.error);
}
}
// Undo/redo is owned by the builder editor session state. It includes the live
// graph, chain, sender, modules, and objects, even when the current graph cannot
// be exported as a valid PTBDoc yet.
if (canUndo) undo();
if (canRedo) redo();
// Capture is side-effect-free; exportDocResult still reports UI export errors.
const current = captureCurrentDocResult();
if (current.ok) {
console.log(current.doc);
}
// Load graph from on-chain transaction digest. Pure raw inputs that can be
// decoded losslessly are materialized for display, using fetched Move function
// signatures when the consumer type needs them.
const chainLoadResult = await loadFromOnChainTx('sui:testnet', '0x1234…');
if (!chainLoadResult.ok) {
console.warn(chainLoadResult.error);
}
// Load an on-chain transaction as an editable template instead of a read-only viewer.
const editableLoadResult = await loadFromOnChainTx('sui:testnet', '0x1234…', {
mode: 'editable',
});
if (!editableLoadResult.ok) {
console.warn(editableLoadResult.error);
}
// 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 is a self-contained bundle of every theme token file so you can switch themes at runtime with setTheme. Importing it includes all shipped themes; the package build emits it at roughly 43 kB before gzip.import '@zktx.io/ptb-builder/styles/theme-dark.css';. Picking a single one keeps the bundle lean while still allowing dynamic switching between the themes you explicitly include.themes-all.css together with individual theme-*.css files. Choose the aggregate file for runtime theme switching, or choose individual theme files for a smaller static bundle.theme value (e.g., theme="light") and set showThemeSelector={false} so the UI doesn’t expose choices that aren’t bundled.onDocChangeonDocChange emissions briefly and debounces viewport-only changes by 250 ms so autosave targets are not overwhelmed while the user edits or pans the canvas.loadFromDoc(doc) imports a document as a new editable baseline and applies the document's saved viewport. loadFromDoc(chain) creates a fresh editable document for that chain and applies the default viewport. These transitions do not infer the saved baseline from a post-load viewport fit.undo() and redo() use builder-owned editor session history, not onDocChange. The history source of truth is the editor state (graph, chain, sender, modules, and objects) and excludes viewport-only changes. When the restored state can be serialized as a PTBDoc, the builder immediately attempts an onDocChange emission for autosave; invalid-but-editable graphs still restore and remain undoable while autosave/export reports document diagnostics.loadFromOnChainTx(..., { mode: 'editable' }) computes the generated transaction layout from a fixed transaction-load target, not from the previously visible viewport, before applying provider state. It then emits that laid-out graph with the default editable viewport as the editable baseline. The default read-only loadFromOnChainTx path does not emit onDocChange, because read-only transaction inspection is not an editable document autosave event.captureCurrentDocResult() captures the live React Flow graph and viewport without setting UI error state or showing a toast. Use it for explicit document export or custom host flows, not as the source of builder undo/redo.address prop is the runtime envelope sender and Assets-modal owner address. It does not become PTBDoc.sender; imported document senders are preserved, and export helpers use an explicit sender option when the host wants a saved document sender.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. |
initialChain | Chain | – | Optional chain used to create a fresh editable PTB on mount. Later document changes should use loadFromDoc(). |
className | string | – | Optional class for a host-controlled container around the builder. |
style | React.CSSProperties | – | Optional style for the same container; useful for setting width/height directly on <PTBBuilder />. |
showThemeSelector | boolean | true | Renders the theme dropdown in the CodePip panel. |
address | string | – | Optional runtime envelope sender and owner address for the Assets modal. Short or canonical Sui address forms are accepted and normalized before runtime helpers use them. It is not substituted into graph arguments. |
gasBudget | number | bigint | string | – | Optional runtime envelope gas budget. String values must be unsigned u64 strings. |
executeTx | (chain: Chain, tx?: Transaction) => Promise<{ digest?: string; error?: string }> | – | Host-provided execution adapter. |
simulateTx | (chain: Chain, tx?: Transaction) => Promise<{ success?: boolean; error?: string }> | – | Optional host-provided simulation adapter; required only when the Dry run action is used. |
createClient | (chain: Chain) => ClientWithCoreApi | gRPC | Optional host-provided SDK Core client factory for read/load paths. |
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:
ptb_4sui:testnet){ x, y, zoom }This enables saving, sharing, and reloading graphs consistently across environments.
useCurrentNetwork() and useDAppKit().switchNetwork() from
@mysten/dapp-kit-react 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 67 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.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.