Transaction Building
Build and sign BSV transactions using the @1sat/actions system and @1sat/core utilities.
The Action System
All 1sat-sdk operations use the BRC-100 action pattern:
createContext() → action.execute(ctx, input) → { txid, rawtx, error }
Actions work with any BRC-100 compatible wallet (OneSatWallet, browser extension, etc).
Context Setup
import { createContext } from '@1sat/actions'
// Basic context (wallet only)
const ctx = createContext(wallet)
// Full context (wallet + services for network operations)
const ctx = createContext(wallet, { services })
Send BSV
import { sendBsv, createContext } from '@1sat/actions'
const ctx = createContext(wallet)
// Simple payment
const result = await sendBsv.execute(ctx, {
requests: [
{ address: '1Recipient...', satoshis: 50000 },
],
})
// Multiple recipients (batch payment)
const result = await sendBsv.execute(ctx, {
requests: [
{ address: '1Alice...', satoshis: 10000 },
{ address: '1Bob...', satoshis: 20000 },
{ address: '1Charlie...', satoshis: 30000 },
],
})
// Custom locking script
const result = await sendBsv.execute(ctx, {
requests: [
{ script: '76a914...88ac', satoshis: 5000 }, // hex locking script
],
})
// OP_RETURN data
const result = await sendBsv.execute(ctx, {
requests: [
{ data: ['hello', 'world'], satoshis: 0 },
],
})
// Payment with inscription
const result = await sendBsv.execute(ctx, {
requests: [
{
address: '1Recipient...',
satoshis: 1,
inscription: {
base64Data: btoa('Hello on-chain'),
mimeType: 'text/plain',
},
},
],
})
Send All BSV
import { sendAllBsv, createContext } from '@1sat/actions'
const ctx = createContext(wallet)
// Send entire wallet balance to one address
const result = await sendAllBsv.execute(ctx, {
destination: '1Recipient...',
})
Sign Messages (BSM)
import { signMessage, createContext } from '@1sat/actions'
const ctx = createContext(wallet)
const result = await signMessage.execute(ctx, {
message: 'Hello, I own this wallet',
encoding: 'utf8', // 'utf8' | 'hex' | 'base64'
})
// result: { address, pubKey, message, sig }
// sig is base64-encoded compact signature (BSM format)
// With derivation tag for domain-specific keys
const result = await signMessage.execute(ctx, {
message: 'Login to example.com',
tag: {
label: 'auth',
id: 'session123',
domain: 'example.com',
meta: {},
},
})
The Two-Phase Signing Pattern
For operations involving custom scripts (ordinals, locks, tokens), the SDK uses a two-phase approach:
Phase 1: createAction (build the transaction)
const createResult = await wallet.createAction({
description: 'Transfer ordinal',
inputBEEF: beefData, // BEEF containing source transactions
inputs: [{
outpoint: 'txid.vout',
inputDescription: 'Ordinal to transfer',
unlockingScriptLength: 108, // Expected script size
}],
outputs: [{
lockingScript: '76a914...88ac',
satoshis: 1,
outputDescription: 'Transferred ordinal',
basket: 'ordinals',
tags: ['type:image/png', 'origin:abc...'],
customInstructions: JSON.stringify({ protocolID, keyID }),
}],
options: {
signAndProcess: false, // Don't auto-sign yet
randomizeOutputs: false, // Preserve output order
},
})
Phase 2: signAction (provide unlocking scripts)
// Get the signable transaction
const tx = Transaction.fromBEEF(createResult.signableTransaction.tx)
// Build custom unlocking scripts per input
const spends = {
0: { unlockingScript: myUnlockingScript.toHex() },
}
// Complete the transaction
const signResult = await wallet.signAction({
reference: createResult.signableTransaction.reference,
spends,
options: { acceptDelayedBroadcast: false },
})
// signResult.txid is the broadcast transaction ID
Action Registry
All actions are registered in a global registry:
import { actionRegistry } from '@1sat/actions'
// List all registered actions
for (const action of actionRegistry.list()) {
console.log(`${action.meta.name} (${action.meta.category})`)
}
// Get a specific action
const send = actionRegistry.get('sendBsv')
// Convert to MCP tool definitions
const tools = actionRegistry.toMcpTools()
All Registered Actions
| Category | Actions |
|---|---|
payments |
sendBsv, sendAllBsv |
ordinals |
getOrdinals, transferOrdinals, listOrdinal, cancelListing, purchaseOrdinal, deriveCancelAddress |
tokens |
listTokens, getBsv21Balances, sendBsv21, purchaseBsv21 |
inscriptions |
inscribe |
locks |
getLockData, lockBsv, unlockBsv |
signing |
signMessage |
sweep |
sweepBsv, sweepOrdinals, sweepBsv21 |
opns |
opnsRegister, opnsDeregister |
Using @1sat/core for Low-Level Tx Building
For operations outside the action system, use @1sat/core directly:
import {
createOrdinals, sendOrdinals, transferOrdTokens,
createOrdListings, purchaseOrdListing,
sendUtxos, deployBsv21Token, burnOrdinals,
TxBuilder, createTxBuilder,
} from '@1sat/core'
// TxBuilder for custom transaction construction
const builder = createTxBuilder({
utxos: paymentUtxos,
paymentPk: privateKey,
changeAddress: myAddress,
satsPerKb: 50,
})
Protocol Helpers
import {
// Sigma protocol (on-chain signatures)
createSigma, signData,
// MAP protocol (metadata)
buildMapScript, createMap, isValidMap,
// Inscription envelope
buildInscriptionEnvelope, createInscription,
// OrdP2PKH template
createOrdP2PKHScript, OrdP2PKH,
// OrdLock template (marketplace listings)
createOrdLockScript, OrdLock,
} from '@1sat/core'
Baskets and Tags
The wallet organizes outputs into baskets:
| Basket | Contents |
|---|---|
ordinals |
Ordinal inscriptions (NFTs) |
bsv21 |
BSV-21 fungible tokens |
locks |
Time-locked BSV |
opns |
OpNS name ordinals |
Tags on outputs provide metadata for filtering:
const result = await wallet.listOutputs({
basket: 'ordinals',
includeTags: true,
includeCustomInstructions: true,
include: 'entire transactions', // include BEEF for spending
limit: 100,
})
Requirements
bun add @1sat/actions @1sat/core @bsv/sdk