| name | transaction-building |
| description | This skill should be used when building BSV transactions with the 1sat-sdk — sending BSV payments, batch payments, OP_RETURN data, custom scripts, signing workflows (createAction/signAction), or understanding the BRC-100 action system. Triggers on 'send BSV', 'build transaction', 'batch payment', 'OP_RETURN', 'createAction', 'signAction', 'sign transaction', 'payment requests', 'locking script', 'action registry', or 'BRC-100'. Uses @1sat/actions and @1sat/core packages. |
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'
const ctx = createContext(wallet)
const ctx = createContext(wallet, { services })
Send BSV
import { sendBsv, createContext } from '@1sat/actions'
const ctx = createContext(wallet)
const result = await sendBsv.execute(ctx, {
requests: [
{ address: '1Recipient...', satoshis: 50000 },
],
})
const result = await sendBsv.execute(ctx, {
requests: [
{ address: '1Alice...', satoshis: 10000 },
{ address: '1Bob...', satoshis: 20000 },
{ address: '1Charlie...', satoshis: 30000 },
],
})
const result = await sendBsv.execute(ctx, {
requests: [
{ script: '76a914...88ac', satoshis: 5000 },
],
})
const result = await sendBsv.execute(ctx, {
requests: [
{ data: ['hello', 'world'], satoshis: 0 },
],
})
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)
const result = await sendAllBsv.execute(ctx, {
destination: '1Recipient...',
})
Sign Messages (BSM)
import { signBsm, createContext } from '@1sat/actions'
const ctx = createContext(wallet)
const result = await signBsm.execute(ctx, {
message: 'Hello, I own this wallet',
encoding: 'utf8',
})
const result = await signBsm.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.
inputBEEF: When Required vs Optional
inputBEEF provides the SPV proof chain for the inputs being spent:
- Optional for inputs from your own wallet — actions auto-resolve BEEF via the output's ID tag.
- Required for external inputs not in your wallet — e.g. purchasing a listing from another user. The buyer's wallet has no BEEF for the seller's ordlock UTXO, so the marketplace/overlay must provide it.
Phase 1: createAction (build the transaction)
const createResult = await wallet.createAction({
description: 'Transfer ordinal',
inputBEEF: beefData,
inputs: [{
outpoint: 'txid.vout',
inputDescription: 'Ordinal to transfer',
unlockingScriptLength: 108,
}],
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,
randomizeOutputs: false,
},
})
Phase 2: completeSignedAction (sign and finalize)
Use the completeSignedAction helper for all two-phase actions. It handles BEEF merge (fixing stripped merkle proofs), script verification, signAction, and automatic abortAction on failure.
import { completeSignedAction } from '@1sat/actions'
const result = await completeSignedAction(
wallet,
createResult,
inputBEEF as number[],
async (tx) => {
const spends: Record<number, { unlockingScript: string }> = {}
spends[0] = { unlockingScript: myUnlockingScript.toHex() }
return spends
},
)
Why not raw signAction? The signable transaction BEEF from createAction has merkle proofs stripped (wallet-toolbox uses mergeRawTx internally). completeSignedAction merges the unsigned tx back into inputBEEF to reconstruct the full proof chain before signing. It also verifies unlocking scripts against locking scripts before submitting, and aborts the action automatically if signing fails.
Action Registry
All actions are registered in a global registry:
import { actionRegistry } from '@1sat/actions'
for (const action of actionRegistry.list()) {
console.log(`${action.meta.name} (${action.meta.category})`)
}
const send = actionRegistry.get('sendBsv')
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 | signBsm |
social | createSocialPost |
identity | publishIdentity, attest, updateProfile, getProfile |
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'
const builder = createTxBuilder({
utxos: paymentUtxos,
paymentPk: privateKey,
changeAddress: myAddress,
satsPerKb: 50,
})
Protocol Helpers
import {
createSigma, signData,
buildMapScript, createMap, isValidMap,
} from '@1sat/core'
import {
Inscription, OrdP2PKH, OrdLock, Lock, BSV20, BSV21,
} from '@1sat/templates'
@1sat/templates provides the script template classes for all 1Sat protocols (Inscription, OrdP2PKH, OrdLock, Lock, BSV20, BSV21). These are also re-exported by @1sat/core for convenience.
Identity Actions
The action system includes BAP identity operations that work with any BRC-100 wallet:
import { publishIdentity, updateProfile, getProfile, attest, resolveBapId, createContext } from '@1sat/actions'
const ctx = createContext({ wallet, chain: 'main' })
const result = await publishIdentity.execute(ctx, { signedScript: signedScript.toHex() })
await updateProfile.execute(ctx, { profile: { name: 'Alice', description: 'Builder' } })
const { bapId, profile } = await getProfile.execute(ctx, {})
await attest.execute(ctx, { attestationHash: 'sha256-of-urn:bap:id:...', counter: '0' })
const bapId = await resolveBapId(ctx)
Identity outputs are stored in the bap basket with tags like type:id and bapId:<hash>.
Baskets and Tags
The wallet organizes outputs into baskets:
| Basket | Contents |
|---|
ordinals | Ordinal inscriptions (NFTs) |
bsv21 | BSV-21 fungible tokens |
bsocial | Social protocol outputs (posts, likes, follows) |
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',
limit: 100,
})
Requirements
bun add @1sat/actions @1sat/core @bsv/sdk