The Watchtower API is a security monitoring service for the RewindBitcoin Wallet that protects users' Bitcoin vaults from unauthorized access. It continuously watches the blockchain for specific transactions that would indicate someone is attempting to unfreeze a vault. When such activity is detected, the service immediately sends system-level push notifications to the user's iOS and Android devices where the RewindBitcoin app is installed, allowing them to take action before funds can be moved.
In practical terms, this means:
- If someone gains access to your wallet (through theft, etc.), the Watchtower notifies you immediately
- When an attacker tries to unfreeze your vault, you receive alerts on all your registered devices as high-priority system notifications
- The RewindBitcoin app immediately displays emergency action options when you tap on the notification
- You then have time to execute a "panic transaction" to move funds to your emergency cold storage before the attacker can access them
- The service works silently in the background, only alerting you when necessary
By default, the RewindBitcoin app uses our hosted Watchtower service, but for enhanced privacy, you can also run your own Watchtower instance and configure the app to use it instead.
If you prefer to run your own Watchtower for enhanced privacy and control over your vault monitoring data, you can easily set it up:
You can easily run the Watchtower API directly using npx without installing it:
npx @rewindbitcoin/watchtower
After starting your own Watchtower, you'll need to configure the RewindBitcoin app to use it:
- Open the RewindBitcoin app
- Go to Settings → Watchtower
- Enter your custom Watchtower URL (e.g.,
http://192.168.1.10:3000
orhttp://your-domain.com
) - Save your settings
Your app will now use your personal Watchtower instance instead of the default service, giving you complete control over your vault monitoring.
# Run with specific port
npx @rewindbitcoin/watchtower --port 3000
# Disable specific networks
npx @rewindbitcoin/watchtower --disable-testnet --disable-tape
# Enable regtest with custom Esplora API URL
npx @rewindbitcoin/watchtower --enable-regtest \
http://localhost:3002
# Enable commitment verification
npx @rewindbitcoin/watchtower --with-commitments
# Specify custom database folder (default is ./db)
npx @rewindbitcoin/watchtower --db-folder /path/to/database
# Display help information
npx @rewindbitcoin/watchtower --help
If no port is specified, a random available port will be used and displayed in the console.
By default, the watchtower monitors these networks:
bitcoin
testnet
-
tape
(Rewind Bitcoin's own test network)
The regtest
network is disabled by default and must be explicitly enabled with
a valid Esplora API URL.
If you want to modify or contribute to the Watchtower API:
git clone https://github.com/rewindbitcoin/watchtower.git
cd watchtower
npm install
# Run in development mode
npx ts-node src/index.ts
# Build the project
npm run build
# Run the built version
npm start
# Build and publish to npm
npm publish
The Watchtower uses an efficient monitoring strategy to minimize API calls:
-
On startup: Initialize with the last checked block height minus IRREVERSIBLE_THRESHOLD from the database or current height (minus IRREVERSIBLE_THRESHOLD) if starting fresh.
-
For each monitoring cycle:
- Get all new blocks since the last checked height
- Check if any pending transactions appear in these blocks
- Send notifications to all devices for found transactions
- Mark notifications as 'sent' or 'pending'
- Update the last checked height
-
Reorg handling: Recheck the last 4 blocks (IRREVERSIBLE_THRESHOLD) on each cycle to handle potential blockchain reorganizations
-
In-memory caching: Keep track of checked blocks in memory to avoid redundant processing within a session
-
Commitment verification: An optional security feature that validates transactions against their original vault creation transaction (See the Commitment Verification section for complete details)
This approach efficiently monitors transactions while handling multiple devices per vault and maintaining proper notification state.
The Watchtower API uses a commitment verification system to prevent abuse:
- When enabled with
--with-commitments
flag, each vault registration requires a valid commitment transaction - The
commitment
is the actual transaction that created the vault on the blockchain - This transaction should have paid a fee to an authorized service address
- The transactions being monitored by the watchtower are those that spend from this commitment (i.e., transactions that initiate the unfreezing of a vault)
- This commitment system serves several important purposes:
- Prevents spam registrations by requiring a real on-chain payment
- Ensures only legitimate vaults can be registered with the service
- Each commitment can only be used for one vault ID
- Note: If you're running your own private watchtower, you can disable this feature as long as you don't make your service publicly available
The service uses Expo Push Notifications to deliver critical security alerts directly to users' iOS and Android devices when a monitored vault is accessed. These notifications appear as system-level alert dialogs with warning messages, ensuring users are promptly informed of potential security events.
When a vault access attempt is detected, the Watchtower immediately sends a push notification to all registered devices associated with that vault. These notifications:
- Appear as high-priority system dialogs on both iOS and Android devices
- Display clear warning messages about the vault being accessed
- Include essential information about which vault is affected
- Provide context about the wallet and transaction
Example Payload:
{
"to": "ExponentPushToken[xyz]",
"title": "Vault Access Alert!",
"body": "Your vault vault123 in wallet 'My Bitcoin Wallet' is being accessed!",
"data": {
"vaultId": "vault123",
"walletUUID": "wallet_unique_identifier",
"walletName": "My Bitcoin Wallet",
"vaultNumber": 1,
"watchtowerId": "client_provided_unique_id_for_watchtower",
"txid": "abcdef1234567890abcdef1234567890",
"attemptCount": 1,
"firstDetectedAt": 1634567890
}
}
The Watchtower implements a robust retry mechanism to ensure critical security alerts are not missed:
- Persistent Retries: The system periodically retries sending notifications until the user explicitly acknowledges receipt
-
Escalating Schedule:
- First day: Retry every 6 hours
- After first day: Retry once per day for up to a week
- Enhanced Context: Retry notifications include additional information about when the issue was first detected
This persistent approach ensures that even if a user's device is temporarily offline or notifications are missed, they will still be alerted to potential security issues with their vaults.
Example Retry Payload:
{
"to": "ExponentPushToken[xyz]",
"title": "Vault Access Alert!",
"body": "Your vault vault123 in wallet 'My Bitcoin Wallet' is being accessed! (Attempt 3, first detected 14 hours ago)",
"data": {
"vaultId": "vault123",
"walletUUID": "wallet_unique_identifier",
"walletName": "My Bitcoin Wallet",
"vaultNumber": 1,
"watchtowerId": "client_provided_unique_id_for_watchtower",
"txid": "abcdef1234567890abcdef1234567890",
"attemptCount": 3,
"firstDetectedAt": 1634567890
}
}
To stop receiving retry notifications once the alert has been seen, the client app should acknowledge receipt using the Acknowledge Notification Receipt API endpoint:
POST /watchtower/ack
{
"pushToken": "ExponentPushToken[xyz]",
"vaultId": "vault123"
}
This acknowledgment system ensures that users are only notified until they've confirmed awareness of the security event.
The Watchtower provides a simple REST API for registering vaults and acknowledging notifications. Below are the available endpoints:
GET /generate_204
- Purpose: Checks if the service is running.
-
Response:
204 No Content
POST /watchtower/register
or POST /:networkId/watchtower/register
-
Purpose: Registers vaults and associates them with a push notification token.
-
URL Parameters:
-
networkId
: The Bitcoin network (bitcoin
,testnet
,tape
, orregtest
) - If using
/watchtower/register
without networkId, defaults tobitcoin
mainnet
-
-
Request Body:
{ "pushToken": "ExponentPushToken[xyz]", "walletUUID": "wallet_unique_identifier", // Unique ID for the wallet "walletName": "My Bitcoin Wallet", "watchtowerId": "client_provided_unique_id_for_watchtower", // Client-provided unique ID for this watchtower instance "locale": "es", // Optional, defaults to "en" "vaults": [ { "vaultId": "vault123", "vaultNumber": 1, "triggerTxIds": ["txid1", "txid2"], "commitment": "0200000001abcdef..." // Required when commitment verification is enabled }, { "vaultId": "vault456", "vaultNumber": 2, "triggerTxIds": ["txid3", "txid4"], "commitment": "0200000001ghijkl..." // Required when commitment verification is enabled } ] }
The Watchtower API currently supports the following languages for notifications:
- English (
en
) - Default - Spanish (
es
)
The language is specified using the locale
parameter during vault registration.
-
Responses:
-
200 OK
: Registration successful -
400 Bad Request
: Invalid input data or commitment transaction -
403 Forbidden
: Commitment transaction doesn't pay to an authorized address -
409 Conflict
: Vault has already been accessed and cannot be registered again
-
POST /watchtower/ack
or POST /:networkId/watchtower/ack
-
Purpose: Acknowledges receipt of a notification for a specific vault.
-
URL Parameters:
-
networkId
: The Bitcoin network (bitcoin
,testnet
,tape
, orregtest
) - If using
/watchtower/ack
without networkId, defaults tobitcoin
mainnet
-
-
Request Body:
{ "pushToken": "ExponentPushToken[xyz]", "vaultId": "vault123" }
-
Responses:
-
200 OK
: Acknowledgment successful -
400 Bad Request
: Invalid input data -
404 Not Found
: No matching notification found -
500 Internal Server Error
: Server error
-
POST /watchtower/notifications
or POST /:networkId/watchtower/notifications
-
Purpose: Retrieves all unacknowledged notifications for a specific device from a specific network.
-
URL Parameters:
-
networkId
: The Bitcoin network (bitcoin
,testnet
,tape
, orregtest
) - If using
/watchtower/notifications
without networkId, defaults tobitcoin
mainnet
-
-
Request Body:
{ "pushToken": "ExponentPushToken[xyz]" }
-
Response:
{ "notifications": [ { "vaultId": "vault123", "walletUUID": "wallet_unique_identifier", "walletName": "My Bitcoin Wallet", "vaultNumber": 1, "watchtowerId": "client_provided_unique_id_for_watchtower", "txid": "abcdef1234567890abcdef1234567890", "attemptCount": 3, "firstDetectedAt": 1634567890, "networkId": "bitcoin" }, { "vaultId": "vault456", "walletUUID": "wallet_unique_identifier", "walletName": "My Bitcoin Wallet", "vaultNumber": 2, "watchtowerId": "client_provided_unique_id_for_watchtower", "txid": "fedcba0987654321fedcba0987654321", "attemptCount": 1, "firstDetectedAt": 1634567891, "networkId": "bitcoin" } ] }
-
Responses:
-
200 OK
: Request successful (even if no notifications are found) -
400 Bad Request
: Invalid input data (missing pushToken) -
500 Internal Server Error
: Server error
-
The Watchtower API uses SQLite with the following structure:
Notifications Table:
Column | Type | Description |
---|---|---|
pushToken |
TEXT | Device push notification token |
vaultId |
TEXT | Associated vault ID |
walletUUID |
TEXT | Unique ID for the wallet |
walletName |
TEXT | Name of the wallet containing the vault |
vaultNumber |
INTEGER | The nth vault created in the wallet |
watchtowerId |
TEXT | Client-provided unique ID for the watchtower instance used for registration |
firstAttemptAt |
INTEGER | Unix timestamp of first notification attempt |
acknowledged |
INTEGER | Whether notification was acknowledged (0=no, 1=yes) |
lastAttemptAt |
INTEGER | Unix timestamp of last notification attempt |
attemptCount |
INTEGER | Number of notification attempts made |
locale |
TEXT | User's preferred language (default: 'en') |
Vault Transactions Table:
Column | Type | Description |
---|---|---|
txid |
TEXT | Primary Key - Transaction ID to monitor |
vaultId |
TEXT | Associated vault ID |
status |
TEXT | Status: 'unchecked', 'unseen', 'reversible', 'irreversible' |
commitmentTxid |
TEXT | The txid of the commitment transaction (when using commitments) |
Commitments Table:
Column | Type | Description |
---|---|---|
txid |
TEXT | Primary Key - Transaction ID of the commitment |
vaultId |
TEXT | Associated vault ID |
created_at |
INTEGER | Unix timestamp when the commitment was registered |
Network State Table:
Column | Type | Description |
---|---|---|
id |
INTEGER | Primary Key (always 1) |
last_checked_height |
INTEGER | Last block height that was checked |
Authorized Addresses Table (in separate database):
Column | Type | Description |
---|---|---|
address |
TEXT | Primary Key - Btc address authorized to use the service |
created_at |
TIMESTAMP | When the address was added to the database |
This table is stored in a separate database file ({networkId}.sqlite
) and is
managed by another process. The watchtower only reads from this database when
commitment verification is enabled.
The Watchtower API efficiently tracks Bitcoin transactions related to vaults and notifies users when their funds are accessed. It is designed to minimize redundant API calls and maximize efficiency in blockchain polling.
By combining commitment verification, efficient blockchain monitoring, and persistent push notifications, the Watchtower provides a robust security layer for Bitcoin vault users.