This skill should be used when working with time-locked BSV — locking satoshis until a specific block height, checking lock status, unlocking matured locks, or understanding the lock script. Triggers on 'lock BSV', 'time lock', 'timelock', 'block height lock', 'unlock BSV', 'matured locks', 'lock data', or 'CLTV lock'. Uses @1sat/actions locks module.
From 1satnpx claudepluginhub b-open-io/claude-plugins --plugin 1satThis skill uses the workspace's default tool permissions.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Guides idea refinement into designs: explores context, asks questions one-by-one, proposes approaches, presents sections for approval, writes/review specs before coding.
Lock and unlock BSV until specific block heights using @1sat/actions.
| Action | Description |
|---|---|
getLockData | Get summary of all locks (total, unlockable, next unlock) |
lockBsv | Lock BSV until a specific block height |
unlockBsv | Unlock all matured (expired) locks |
import { getLockData, createContext } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const data = await getLockData.execute(ctx, {})
console.log(`Total locked: ${data.totalLocked} sats`)
console.log(`Unlockable now: ${data.unlockable} sats`)
console.log(`Next unlock at block: ${data.nextUnlock}`)
interface LockData {
totalLocked: number // Total satoshis in all locks
unlockable: number // Satoshis that can be unlocked now
nextUnlock: number // Block height of next maturing lock
}
import { lockBsv, createContext } from '@1sat/actions'
const ctx = createContext(wallet, { services })
// Lock 10,000 sats until block 900000
const result = await lockBsv.execute(ctx, {
requests: [
{ satoshis: 10000, until: 900000 },
],
})
// Multiple locks in one transaction
const result = await lockBsv.execute(ctx, {
requests: [
{ satoshis: 5000, until: 880000 },
{ satoshis: 10000, until: 900000 },
{ satoshis: 50000, until: 950000 },
],
})
if (result.txid) {
console.log('Locked! txid:', result.txid)
}
import { unlockBsv, createContext } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const result = await unlockBsv.execute(ctx, {})
if (result.txid) {
console.log('Unlocked! txid:', result.txid)
} else if (result.error === 'no-matured-locks') {
console.log('No locks ready to unlock yet')
}
locks basketuntil: tag against current block heightcreateAction with signAndProcess: false to build the unsigned transactioncompleteSignedAction which handles BEEF merge, signing, script verification, signAction, and abort on failureLock.unlockWithWallet per inputAll two-phase unlock operations use the completeSignedAction helper:
import { completeSignedAction } from '@1sat/actions'
import { Lock } from '@1sat/templates'
const result = await completeSignedAction(
ctx.wallet,
createResult,
inputBEEF as number[],
async (tx) => {
const spends: Record<number, { unlockingScript: string }> = {}
for (let i = 0; i < maturedLocks.length; i++) {
const lock = maturedLocks[i]
const unlocker = Lock.unlockWithWallet(
ctx.wallet,
lock.protocolID,
lock.keyID,
'self',
)
const unlockingScript = await unlocker.sign(tx, i)
spends[i] = { unlockingScript: unlockingScript.toHex() }
}
return spends
},
)
The helper handles:
inputBEEF (fixes stripped merkle proofs)TransactionsignAction to finalizeabortAction automatically on failureThe lock script combines a CLTV (CheckLockTimeVerify) check with a P2PKH signature check:
<lockPrefix> <pubKeyHash> <blockHeight> <lockSuffix>
To unlock:
nLockTime must be >= the lock's block heightsequenceNumber must be 0 (enables nLockTime checking)When building createAction inputs for lock outputs:
unlockingScriptLength: 1205 (accounts for DER signature variability)sequenceNumber: 0 (required for nLockTime)anyoneCanPay must be false (the default)Locks are stored in the locks basket with tags:
| Tag | Meaning |
|---|---|
until:{height} | Block height when the lock matures |
Custom instructions store the protocol and key info for unlocking.
There is a minimum unlock threshold (MIN_UNLOCK_SATS) to prevent dust unlock attempts. If your matured locks total less than this threshold per lock, they won't appear as unlockable.
The lock module uses services.chaintracks.currentHeight() to determine the current block height. You can also check it directly:
const res = await fetch('https://api.1sat.app/1sat/chaintracks/height')
const height = await res.json()
console.log('Current height:', height)
bun add @1sat/actions @1sat/wallet @bsv/sdk
Unlock operations require services for current block height lookup.