Skip to content
LogoLogo

PST

Format and roles: PST concept.

FunctionRole
create_pst(tx, spent, network, flags=0)creator
update_pst(pst, redeem=…, origins=…)updater: add redeem data and key origins
sign_pst(pst, signer)signer (keys or a seed)
combine_pst(*psts)combiner: verify and merge
finalize_pst(pst, preimages=None)finalizer: build witnesses, returns the signed transaction
decode_pst(data) / encode_pst(pst, format="base64")text or binary ↔ PST
pst_status(pst)which inputs still need signatures
pst = create_pst(tx, spent, "regtest")
a = sign_pst(encode_pst(pst), alice_keychain)
b = sign_pst(encode_pst(pst), bob_keychain)
client.send_transaction(tx=finalize_pst(combine_pst(a, b)))

Example

/**
 * 2-of-3 multisig with a PST: fund a shared vault, then two co-signers sign independently.
 *
 *   ARREKTA_URL=http://127.0.0.1:28332 ARREKTA_ADMIN_TOKEN=... npx tsx examples/multisig-pst.ts
 */
import {
  addPstOrigins,
  addPstRedeem,
  combinePst,
  createClient,
  createMultisigPolicy,
  createPst,
  createTransaction,
  encodeAddress,
  equalLocks,
  finalizePst,
  fromHex,
  generateSeed,
  getChain,
  getCoins,
  isPstComplete,
  keychainFromSeed,
  mine,
  multisigLock,
  pstFromBase64,
  pstToBase64,
  sendPayment,
  sendTransaction,
  signPstWithSeed,
  txid,
} from 'arrekta-sdk';
 
const URL = process.env.ARREKTA_URL ?? 'http://127.0.0.1:28332';
const ADMIN = process.env.ARREKTA_ADMIN_TOKEN;
const SEED = fromHex(process.env.ARREKTA_SEED ?? 'a1'.repeat(32));
 
// 1. Connect; a funder and three co-signers.
const client = createClient({ url: URL, adminToken: ADMIN });
const { network } = await getChain(client);
const funder = keychainFromSeed(SEED, { network });
const seeds = [generateSeed(), generateSeed(), generateSeed()];
const cosigners = seeds.map((s) => keychainFromSeed(s, { network }));
if ((await getCoins(client, { wallet: funder })).length === 0) await mine(client, funder.address(0), 101);
 
// 2. The 2-of-3 policy and its address.
const policy = createMultisigPolicy(2, cosigners.map((kc) => kc.key(0)));
const vaultLock = multisigLock(policy);
const vault = encodeAddress(vaultLock, network);
console.log(`vault ${vault}`);
 
// 3. Fund the vault with 4 ARRK and confirm.
const fund = await sendPayment(client, { wallet: funder, to: vault, amount: '4' });
await mine(client, funder.address(0), 1);
const vout = fund.tx.outputs.findIndex((o) => equalLocks(o.lock, vaultLock));
const coin = fund.tx.outputs[vout]!;
 
// 4. The coordinator creates a PST paying 3.9998 ARRK back to the funder.
const tx = createTransaction({
  inputs: [{ prevout: { txid: txid(fund.tx), vout }, sequence: 0xffffffff }],
  outputs: [{ asset: coin.asset, amount: coin.amount - 20_000n, lock: funder.lock(1) }],
});
const unsigned = addPstOrigins(addPstRedeem(createPst(tx, [coin], network), [policy]), cosigners.map((kc) => kc.origin(0)));
const shared = pstToBase64(unsigned); // base64, sent to each co-signer
 
// 5. Co-signers 1 and 3 sign independently.
const signedA = pstToBase64(signPstWithSeed(pstFromBase64(shared), seeds[0]!));
const signedC = pstToBase64(signPstWithSeed(pstFromBase64(shared), seeds[2]!));
 
// 6. Combine, finalize and broadcast.
const combined = combinePst(pstFromBase64(signedA), pstFromBase64(signedC));
console.log('complete:', isPstComplete(combined));
const final = finalizePst(combined);
console.log('sent', (await sendTransaction(client, { tx: final })).txid);
await mine(client, funder.address(0), 1);

Source: sdk/typescript/examples/multisig-pst.ts (updated 2026-11-02) · sdk/python/examples/multisig_pst.py (updated 2026-11-02). Runnable against a regtest node; CI runs every example.