| name | sync-cosign |
| description | This skill should be used when coordinating wallet state across devices or running a cosigner backend — syncing external payments to deposit addresses, polling the message box for inbound payments, pulling cosign-wrapped token deliveries, publishing BAP attestations, or building/validating cosigner-signed BSV21 transfers. Triggers on 'sync addresses', 'sync messages', 'message box', 'cosign delivery', 'attest', 'attestation', 'cosigner', 'cosign transfer', 'multi-device sync', or 'BRC-29 deposit'. Uses @1sat/actions sync and cosign modules. |
Sync & Cosign
Coordinate wallet state across devices and run cosigner-validated token flows using @1sat/actions.
This skill covers two related areas:
- Sync actions pull external value into the wallet: payments to derived deposit addresses, message-box payments, and cosign-wrapped token deliveries.
- Cosign covers BAP attestation plus the cosigner-backend helpers that build and finalize cosigner-signed BSV21 transfers.
Calling Pattern
import { createContext, syncAddresses } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const result = await syncAddresses.execute(ctx, {})
Every sync action sets requiresServices: true — services must be present in the context.
Sync Actions
| Action | Description |
|---|
syncAddresses | Internalize external payments to BRC-29 deposit addresses |
syncMessages | Internalize inbound paymail payments from the message box |
syncCosignDeliveries | Pull cosign-wrapped BSV21 deliveries from a MessageBox |
syncAddresses
Derives BRC-29 deposit addresses (default prefix "1sat"), streams new outputs from the 1sat-stack indexer, classifies them, and internalizes them. Tracks a reorg-safe score in a per-identity store (IndexedDB in browsers, bun:sqlite in Node/Bun) so repeat calls only process new outputs. After internalizing, it rotates plain-BSV inbounds into a fresh BRC-29 funding output via sweepDeposit.
Use this on wallet mount or on a schedule to pick up payments sent to the wallet's deposit addresses from any device.
import { createContext, syncAddresses } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const result = await syncAddresses.execute(ctx, {
prefix: '1sat',
startIndex: 0,
count: 1,
onProgress: (p) => console.log(p),
})
console.log(`processed ${result.processed}, failed ${result.failed}`)
interface SyncAddressesInput {
prefix?: string
startIndex?: number
count?: number
onProgress?: (progress: SyncProgress) => void
}
interface SyncAddressesResult {
processed: number
failed: number
lastScore: number
addresses: string[]
}
syncMessages
Polls a message box (default "payment_inbox") at ${services.baseUrl}/1sat/messagebox using AuthFetch, internalizes each BRC-29 paymail payment, and acknowledges only the messages that internalized successfully.
import { createContext, syncMessages } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const result = await syncMessages.execute(ctx, {
messageBox: 'payment_inbox',
})
console.log(`processed ${result.processed}, failed ${result.failed}`)
interface SyncMessagesInput {
messageBox?: string
}
interface SyncMessagesResult {
processed: number
failed: number
}
syncCosignDeliveries
One-shot pull from a MessageBox slot (default "cosign_token_inbox", server default https://messagebox.1sat.app) used by cosign-wrapped BSV21 mints and transfers. Bodies are BRC-2 ECDH/AES-256-GCM ciphertext; this action unwraps and decrypts them with the wallet, then internalizes the owned output into the bsv21 basket with the supplied customInstructions verbatim. When services is present it enriches tags with token symbol/icon/decimals from the BSV21 overlay.
import { createContext, syncCosignDeliveries } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const result = await syncCosignDeliveries.execute(ctx, {
messageBox: 'cosign_token_inbox',
messageboxUrl: 'https://messagebox.1sat.app',
})
console.log(`processed ${result.processed}, failed ${result.failed}`)
interface SyncCosignDeliveriesInput {
messageBox?: string
messageboxUrl?: string
}
interface SyncCosignDeliveriesResult {
processed: number
failed: number
}
Attest
attest is an Action (publishes a transaction), so it uses the same createContext / .execute pattern. It publishes a BAP ATTEST OP_RETURN (BAP_PREFIX | "ATTEST" | attestationHash | counter) signed with AIP via the current signing key. The output lands in the bap basket. Requires that an identity has already been published (see ../identity/SKILL.md).
import { createContext, attest } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const result = await attest.execute(ctx, {
attestationHash: '<sha256 of urn:bap:id:attribute:value:nonce>',
counter: '0',
})
if (result.error) {
} else {
console.log('Attested. txid:', result.txid)
}
interface AttestRequest extends ActionOptions {
attestationHash: string
counter?: string
}
interface IdentityResponse {
txid?: string
tx?: number[]
bapId?: string
error?: string
}
The output is tagged type:attest, hash:<attestationHash>.
Cosigner Transfer Helpers
These are plain async functions, not Action objects — they do not take a context or use .execute. Each takes a single input object that carries its own wallet and services. They are intended for a cosigner backend that constructs, validates, and broadcasts cosigner-signed BSV21 transfers.
buildCosignDestination
Derives a cosign-wrapped locking script for a recipient using BRC-42 derivation between the caller's wallet and the recipient, embedding the cosigner's identity key as second signer. This one takes a context (it derives via ctx.wallet).
import { createContext, buildCosignDestination } from '@1sat/actions'
const ctx = createContext(wallet, { services })
const { lockingScript, address } = await buildCosignDestination(ctx, {
recipientIdentityKey: '<recipient identity pubkey hex>',
cosignerIdentityKey: '<cosigner identity pubkey hex>',
keyID: '<brc-42 derivation keyID>',
})
interface BuildCosignDestinationInput {
recipientIdentityKey: string
cosignerIdentityKey: string
keyID: string
}
interface BuildCosignDestinationResult {
lockingScript: LockingScript
address: string
}
prepareCosignBsv21Transfer
Phase one of the two-request cosigner flow. The cosigner constructs the unsigned tx (cosign-wrapped transfer outputs, optional multisig holding outputs, optional burns), allocates funding via its own BRC-100 wallet (createAction with signAndProcess: false), persists the session via the supplied CosignSessionStore, and returns the signable BEEF plus per-input sighashes for the sender to sign.
import {
prepareCosignBsv21Transfer,
InMemoryCosignSessionStore,
} from '@1sat/actions'
const sessionStore = new InMemoryCosignSessionStore()
const prepared = await prepareCosignBsv21Transfer({
wallet: cosignerWallet,
services,
tokenId,
tokenInputs: [{ outpoint }],
inputBEEF,
destinations: [{ recipientIdentityKey, amount }],
senderIdentityKey,
sessionStore,
})
interface PrepareCosignBsv21TransferInput {
wallet: WalletInterface
services: OneSatServices
tokenId: string
tokenInputs: CosignTokenInput[]
inputBEEF: number[]
destinations: CosignTransferDestination[]
multisigDestinations?: CosignTransferMultisigDestination[]
burns?: CosignTransferBurn[]
senderIdentityKey: string
sessionStore: CosignSessionStore
}
interface PrepareCosignBsv21TransferResult {
sessionId: string
signableBeef: number[]
reference: string
sighashes: Array<{ inputIndex: number; sighashHex: string }>
}
Sighashes use SIGHASH_ALL | SIGHASH_FORKID (0x41). The sender's wallet signs the single-SHA256 of each sighash.
finalizeCosignBsv21Transfer
Phase two. The sender returns owner-side signatures; the cosigner adds its own signature per cosign-wrapped input, calls signAction to broadcast, submits the resulting BEEF to the BSV21 overlay, and returns per-recipient delivery payloads for messagebox dispatch.
import { finalizeCosignBsv21Transfer } from '@1sat/actions'
const finalized = await finalizeCosignBsv21Transfer({
wallet: cosignerWallet,
services,
sessionId: prepared.sessionId,
ownerSigs: [
{ inputIndex: 0, sigHex: '<der sig hex>', ownerPubkeyHex: '<pubkey hex>' },
],
sessionStore,
})
interface FinalizeCosignBsv21TransferInput {
wallet: WalletInterface
services: OneSatServices
sessionId: string
ownerSigs: CosignOwnerSig[]
sessionStore: CosignSessionStore
}
interface FinalizeCosignBsv21TransferResult {
txid: string
beef: number[]
overlayStatus: string
recipients: CosignRecipientPayload[]
}
Recipients consume their customInstructions via syncCosignDeliveries (above), which internalizes the delivered output into the bsv21 basket.
Session Store
prepare/finalize share state through a CosignSessionStore:
interface CosignSessionStore {
save(session: CosignTransferSession): Promise<void>
load(sessionId: string): Promise<CosignTransferSession | null>
delete(sessionId: string): Promise<void>
}
InMemoryCosignSessionStore is the default for tests and ephemeral demos. Supply a persistent implementation (e.g. Postgres-backed) for a real backend.
The two-phase signing mechanics (signable BEEF rebuild, script verification, signAction, abort on failure) are covered in ../action-patterns/SKILL.md.
Requirements
bun add @1sat/actions @1sat/wallet @1sat/client @bsv/sdk
Sync actions require services in the context. syncCosignDeliveries and the cosign transfer helpers additionally use @bsv/p2p MessageBox and the BSV21 overlay via services.