Lock and unlock BSV until specific block heights using @1sat/actions.
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 |
Check Lock Status
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})
### LockData Response
```typescript
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
}
Lock BSV
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)
}
Unlock Matured Locks
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')
}
How Unlocking Works
- Lists all outputs in the
locksbasket - Checks each lock's
until:tag against current block height - Filters to only matured locks (until <= currentHeight)
- Calls
createActionwithsignAndProcess: falseto build the unsigned transaction - Calls
completeSignedActionwhich handles BEEF merge, signing, script verification,signAction, and abort on failure - The signing callback uses
Lock.unlockWithWalletper input
completeSignedAction Pattern
All 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:
- Merging the signable transaction BEEF with
inputBEEF(fixes stripped merkle proofs) - Calling the signing callback with a fully-wired
Transaction - Verifying unlocking scripts against locking scripts before submitting
- Calling
signActionto finalize - Calling
abortActionautomatically on failure
How the Lock Script Works
The lock script combines a CLTV (CheckLockTimeVerify) check with a P2PKH signature check:
<lockPrefix> <pubKeyHash> <blockHeight> <lockSuffix>
To unlock:
- Transaction
nLockTimemust be >= the lock's block height - Input
sequenceNumbermust be 0 (enables nLockTime checking) - Valid signature from the lock key
- Sighash preimage for script verification
Lock Input Parameters
When building createAction inputs for lock outputs:
unlockingScriptLength: 1205(accounts for DER signature variability)sequenceNumber: 0(required for nLockTime)anyoneCanPaymust befalse(the default)
Lock Storage
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.
Current Block Height
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)
Requirements
bun add @1sat/actions @1sat/wallet @bsv/sdk
Unlock operations require services for current block height lookup.