Skip to main content

Sending PTBs with the TypeScript SDK

A Programmable Transaction Block (PTB) groups several onchain actions into a single transaction that succeeds or fails as a unit. Send one from an app you already have, whether it signs with its own key pair or relies on a user's wallet.

tip

The sections below cover the integration points, not the full API. For complete coverage of transaction building, including inputs, commands, results, and execution, see Sui Programmable Transaction Basics in the TypeScript SDK documentation.

Configure your project for ES modules

The Sui TypeScript SDK ships as an ES module, so your project's TypeScript configuration has to resolve ES modules. In a Node.js backend, that means module and moduleResolution settings such as NodeNext:

{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true
}
}

Frontend build tools such as Vite and Next.js handle this for you. If you do not have a frontend project yet, the dApp Kit starter scaffolds one with the configuration already in place:

$ npm create @mysten/dapp

Send a transaction from a backend script

A backend script signs with a key pair that you control. Load the secret key from an environment variable, never from a hardcoded string.

Install the core SDK package:

$ npm install @mysten/sui

The following script builds a transaction that sends 0.1 SUI to a recipient, signs it, executes it, and checks the result:

import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { Transaction } from '@mysten/sui/transactions';
import { MIST_PER_SUI } from '@mysten/sui/utils';

const client = new SuiGrpcClient({
network: 'devnet',
baseUrl: 'https://fullnode.devnet.sui.io:443',
});

// Load the secret key from the environment, never hardcode it.
const keypair = Ed25519Keypair.fromSecretKey(process.env.SUI_SECRET_KEY!);

// Build a transaction that sends 0.1 SUI to a recipient.
const tx = new Transaction();
tx.transferObjects([tx.coin({ balance: 0.1 * Number(MIST_PER_SUI) })], '0xRecipientAddress');

const result = await keypair.signAndExecuteTransaction({ transaction: tx, client });

if (result.$kind === 'FailedTransaction') {
throw new Error(`Transaction failed: ${result.FailedTransaction.status.error?.message}`);
}

console.log('Transaction digest:', result.Transaction.digest);

The tx.coin method sources the requested amount from your address balance when available and falls back to owned coins, so you do not need to pick coins yourself. The transferObjects command then sends that coin to the recipient.

Always check the $kind field on the result. A transaction can be submitted and still fail onchain, so treat a FailedTransaction result as an error rather than assuming success.

Send a transaction from a frontend wallet

A frontend app does not hold the user's key. The user's wallet signs the transaction through Sui dApp Kit, so your code only builds the transaction and hands it to the wallet.

Install dApp Kit alongside the core SDK:

$ npm install @mysten/dapp-kit-react @mysten/sui

This example assumes a DAppKitProvider wraps your app, configured with createDAppKit. For the provider setup, see Getting started with dApp Kit for React.

The following component builds the transaction in the click handler and asks the connected wallet to sign and execute it:

import { ConnectButton } from '@mysten/dapp-kit-react/ui';
import { useCurrentAccount, useDAppKit } from '@mysten/dapp-kit-react';
import { Transaction } from '@mysten/sui/transactions';
import { MIST_PER_SUI } from '@mysten/sui/utils';

export function SendButton() {
const account = useCurrentAccount();
const dAppKit = useDAppKit();

async function handleSend() {
if (!account) return;

// Build a transaction that sends 0.1 SUI to the connected account.
const tx = new Transaction();
tx.transferObjects([tx.coin({ balance: 0.1 * Number(MIST_PER_SUI) })], account.address);

const result = await dAppKit.signAndExecuteTransaction({ transaction: tx });

if (result.$kind === 'FailedTransaction') {
console.error(result.FailedTransaction.status.error?.message);
return;
}

console.log('Transaction digest:', result.Transaction.digest);
}

return (
<div>
<ConnectButton />
{account && <button onClick={handleSend}>Send</button>}
</div>
);
}

Pass the Transaction instance directly to the wallet. The wallet sets the sender, selects gas coins, and signs, so you never call tx.build() yourself and never touch the user's key.

danger

Never include a secret key or recovery phrase in frontend code. Anything you ship to the browser is public, so a key in frontend code is exposed to everyone who loads the page. In a frontend app, the user's wallet signs the transaction, which means your code never handles their key. Keep secret keys in backend scripts only, and load them from environment variables or a secret manager.