Sentinel API Reference
The Sentinel API allows you to programmatically create and manage sentinels.
Requests need to be authenticated with a bearer token, which is negotiated from the Team API Key with the corresponding capability. Refer to the authentication section for info on how to negotiate it.
We recommend you use the defender-sentinel-client npm package for simplifying interactions with the Sentinel API. |
It is not recommended to use the defender-sentinel-client npm package in a browser environment as sensitive keys would be exposed publicly. |
const { SentinelClient } = require('defender-sentinel-client');
const creds = { apiKey: ADMIN_API_KEY, apiSecret: ADMIN_API_SECRET };
const client = new SentinelClient(creds);
Notifications Endpoint
A sentinel requires a notification configuration to alert the right channels in case an event is triggered. In order to do so, you can either use an existing notification ID (from another sentinel for example), or create a new one.
The following notification channels are available:
-
email
-
slack
-
discord
-
telegram
-
datadog
List Notifications
To list all notification channels, you can call the listNotificationChannels
function on the client, which returns a NotificationResponse[]
object.
// List existing notification channels
// This returns a `NotificationResponse[]` object.
const notificationChannels = await client.listNotificationChannels()
const { notificationId, type } = notificationChannels[0];
Alternatively, via a curl
request:
curl \
-X GET \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
"https://defender-api.openzeppelin.com/sentinel/notifications"
An example response:
[
{
"notificationId": "14e99e8f-e2be-4eb1-a543-b2d90b68cdd8",
"config": {
"emails": [
"john@example.com"
]
},
"updatedAt": "2021-11-04T12:21:22.858Z",
"tenantId": "0c1585a9-1b86-4f9d-8468-28227802ac1d",
"paused": false,
"name": "MyEmailNotification",
"type": "email"
},
]
Create Notifications
To create a new notification channel, you can call the createNotificationChannel
function on the client.
The createNotificationChannel
function requires the CreateNotificationRequest
object and returns a NotificationResponse
object.
// create a new notification
const notification = await client.createNotificationChannel({
type: 'email',
name: 'MyEmailNotification',
config: {
emails: ['john@example.com'],
},
paused: false,
});
const notification = await client.createNotificationChannel({
type: 'slack',
name: 'MySlackNotification',
config: {
url: 'https://slack.com/url/key',
},
paused: false,
});
const notification = await client.createNotificationChannel({
type: 'telegram',
name: 'MyTelegramNotification',
config: {
botToken: 'abcd',
chatId: '123',
},
paused: false,
});
const notification = await client.createNotificationChannel({
type: 'discord',
name: 'MyDiscordNotification',
config: {
url: 'https://discord.com/url/key',
},
paused: false,
});
const notification = await client.createNotificationChannel({
type: 'datadog',
name: 'MyDatadogNotification',
config: {
apiKey: 'abcd',
metricPrefix: 'prefix',
},
paused: false,
});
Update a Notification
To update an existing notification channel, you can call the updateNotificationChannel
function on the client, which returns a NotificationResponse
object. The function takes in the UpdateNotificationRequest
object as a parameter.
const notificationChannel = await client.updateNotificationChannel(
{
type: 'email',
notificationId: '14e99e8f-e2be-4eb1-a543-b2d90b68cdd8',
config: {
emails: ["john@example.com"]
},
paused: false,
name: "MyUpdatedEmailNotification"
});
Get a Notification
To retrieve a notification channel, you can call the getNotificationChannel
function on the client, which returns a NotificationResponse
object. The function takes in the GetNotificationRequest
object as a parameter.
const notificationToRetrieve = {type: 'email', notificationId: '14e99e8f-e2be-4eb1-a543-b2d90b68cdd8'}
const notificationChannel = await client.getNotificationChannel(notificationToRetrieve);
Delete Notifications
To delete a notification channel, you can call the deleteNotificationChannel
function on the client, which returns a string if successful. The function takes in the DeleteNotificationRequest
object as a parameter.
const notificationToDelete = {type: 'email', notificationId: '14e99e8f-e2be-4eb1-a543-b2d90b68cdd8'}
const deleted = await client.deleteNotificationChannel(notificationToDelete);
Sentinels Endpoint
List Sentinels
To list existing sentinels, you can call the list
function on the client, which returns a ListSentinelResponse
object:
await client.list();
The subscribers
endpoint is used to retrieve a list of existing sentinels via a GET
request.
curl \
-X GET \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
"https://defender-api.openzeppelin.com/sentinel/subscribers"
An example response:
[
{
"notifyConfig": {
"notifications": [
{
"type": "email",
"notificationId": "68e494d7-3b5a-4ffe-bd12-d4e483aa4995"
}
],
"timeoutMs": 0
},
"tenantId": "0c1585a9-1b86-4f9d-8468-28227802ac1d",
"createdAt": "2021-11-15T16:04:13.936Z",
"addressRules": [
{
"conditions": [],
"abi": "[...]",
"addresses": ["0xf664FA8aB9AA8021E2c08F45fEeA817D5730A713"]
}
],
"blockWatcherId": "rinkeby-1",
"subscriberId": "abebeda6-f670-4e3c-a65b-a34c840e9a5e",
"paused": false,
"name": "test",
"network": "rinkeby"
}
]
Create Sentinels
To create a new sentinel, you need to provide the network, name, pause-state, conditions, alert threshold and notification configuration. This request is exported as type CreateSentinelRequest
.
type CreateSentinelRequest =
| ExternalCreateBlockSubscriberRequest
| ExternalCreateFortaSubscriberRequest;
interface ExternalCreateBlockSubscriberRequest {
type: 'BLOCK';
name: string;
addresses: string[];
paused?: boolean;
alertThreshold?: Threshold;
autotaskCondition?: string;
autotaskTrigger?: string;
alertTimeoutMs?: number;
alertMessageBody?: string;
notificationChannels: string[];
network: string;
confirmLevel?: number; // blockWatcherId
abi?: string;
eventConditions?: EventCondition[];
functionConditions?: FunctionCondition[];
txCondition?: string;
}
interface ExternalCreateFortaSubscriberRequest {
type: 'FORTA';
name: string;
paused?: boolean;
alertThreshold?: Threshold;
autotaskCondition?: string;
autotaskTrigger?: string;
alertTimeoutMs?: number;
alertMessageBody?: string;
notificationChannels: string[];
network?: string;
fortaLastProcessedTime?: string;
addresses?: Address[];
agentIDs?: string[];
fortaConditions: FortaConditionSet;
privateFortaNodeId?: string;
}
An example for a contract (BLOCK
) sentinel is provided below. This sentinel will be named My New Sentinel
and will be monitoring the renounceOwnership
function on the 0x0f06aB75c7DD497981b75CD82F6566e3a5CAd8f2
contract on the Rinkeby network.
The alert threshold is set to 2 times within 1 hour, and the user will be notified via email.
const requestParameters = {
type: 'BLOCK',
network: 'rinkeby',
// optional
confirmLevel: 1, // if not set, we pick the blockwatcher for the chosen network with the lowest offset
name: 'My New Sentinel',
addresses: ['0x0f06aB75c7DD497981b75CD82F6566e3a5CAd8f2'],
abi: '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{...}]',
// optional
paused: false,
// optional
eventConditions: [],
// optional
functionConditions: [{ functionSignature: 'renounceOwnership()' }],
// optional
txCondition: 'gasPrice > 0',
// optional
autotaskCondition: '3dcfee82-f5bd-43e3-8480-0676e5c28964',
// optional
autotaskTrigger: undefined,
// optional
alertThreshold: {
amount: 2,
windowSeconds: 3600,
},
// optional
alertTimeoutMs: 0,
notificationChannels: [notification.notificationId],
};
If you wish to trigger the sentinel based on additional events, you could add another EventCondition
or FunctionCondition
object, for example:
functionConditions: [{ functionSignature: 'renounceOwnership()' }],
eventConditions: [
{
eventSignature: "OwnershipTransferred(address,address)",
expression: "\"0xf5453Ac1b5A978024F0469ea36Be25887EA812b5,0x6B9501462d48F7e78Ba11c98508ee16d29a03412\""
}
]
You could also apply a transaction condition by modifying the txCondition
property:
Possible variables: value
, gasPrice
, maxFeePerGas
, maxPriorityFeePerGas
, gasLimit
, gasUsed
, to
, from
, nonce
, status
('success', 'failed' or 'any'), input
, or transactionIndex
.
txCondition: 'gasPrice > 0',
You can also construct a request for a Forta (FORTA
) sentinel as follows:
const requestParameters = {
type: 'FORTA',
name: 'MyNewFortaSentinel',
// optional
addresses: ['0x0f06aB75c7DD497981b75CD82F6566e3a5CAd8f2'],
// optional
agentIDs: ['0x8fe07f1a4d33b30be2387293f052c273660c829e9a6965cf7e8d485bcb871083'],
fortaConditions: {
// optional
alertIDs: undefined, // string[]
minimumScannerCount: 1, // default is 1
// optional
severity: 2, // (unknown=0, info=1, low=2, medium=3, high=4, critical=5)
},
// optional
paused: false,
// optional
autotaskCondition: '3dcfee82-f5bd-43e3-8480-0676e5c28964',
// optional
autotaskTrigger: undefined,
// optional
alertThreshold: {
amount: 2,
windowSeconds: 3600,
},
// optional
alertTimeoutMs: 0,
notificationChannels: [notification.notificationId],
};
To create a Forta Local Mode Sentinel specify the scanner node address with privateFortaNodeId
requestParameters.privateFortaNodeId: '0x0f06aB75c7DD497981b75CD82F6566e3a5CAd8f2'
Once all required parameters are populated, you can create a sentinel by calling the create
function on the client. This will return a CreateSentinelResponse
object.
await client.create(requestParameters);
Additionally, the sentinel could invoke an autotask to further evaluate. Documentation around this can be found here: https://docs.openzeppelin.com/defender/sentinel#autotask_conditions.
// If other conditions match, the sentinel will invoke this autotask to further evaluate.
autotaskCondition: '3dcfee82-f5bd-43e3-8480-0676e5c28964',
// Define autotask within the notification configuration
autotaskTrigger: '1abfee11-a5bc-51e5-1180-0675a5b24c61',
The subscribers
endpoint is used to a create new sentinels via a POST
request.
If you wish to call the API directly, you will need to construct a CreateBlockSubscriberRequest
object.
Defender currently only supports a limited subset of Sentinels (only a single addressRule), and we strongly suggest going through the JS client to avoid incompatibilities. |
interface CreateBlockSubscriberRequest {
name: string;
paused: boolean;
alertThreshold?: {
amount: number;
windowSeconds: number;
};
notifyConfig?: {
notifications: [{
notificationId: string;
type: NotificationType;
}];
autotaskId?: string;
messageBody?: string;
timeoutMs: number;
};
addressRules: [{
conditions: ConditionSet[];
autotaskCondition?: {
autotaskId: string;
};
addresses: string[];
abi?: string;
}];
blockWatcherId: string;
network: Network;
type: 'BLOCK';
}
type NotificationType = 'slack' | 'email' | 'discord' | 'telegram' | 'datadog';
interface ConditionSet {
eventConditions: EventCondition[];
txConditions: TxCondition[];
functionConditions: FunctionCondition[];
}
interface EventCondition {
eventSignature: string;
expression?: string | null;
}
interface TxCondition {
status: 'success' | 'failed' | 'any';
expression?: string | null;
}
interface FunctionCondition {
functionSignature: string;
expression?: string | null;
}
curl \
-X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
-d '{...}' \
"https://defender-api.openzeppelin.com/sentinel/subscribers"
Supported networks include:
type Network =
| 'mainnet'
| 'goerli'
| 'sepolia'
| 'xdai'
| 'sokol'
| 'fuse'
| 'bsc'
| 'bsctest'
| 'fantom'
| 'fantomtest'
| 'moonbase'
| 'moonriver'
| 'moonbeam'
| 'matic'
| 'mumbai'
| 'avalanche'
| 'fuji'
| 'optimism'
| 'optimism-kovan'
| 'optimism-goerli'
| 'arbitrum'
| 'arbitrum-nova'
| 'arbitrum-rinkeby'
| 'arbitrum-goerli'
| 'celo'
| 'alfajores'
| 'harmony-s0'
| 'harmony-test-s0'
| 'aurora'
| 'auroratest'
| 'zksync'
| 'zksync-goerli'
| 'base-goerli'
| 'linea-goerli';
Retrieve a Sentinel
You can retrieve a sentinel by ID. This will return a CreateSentinelResponse
object.
await client.get('8181d9e0-88ce-4db0-802a-2b56e2e6a7b1');
The subscribers/{id}
endpoint is used to retrieve a sentinel via a GET
request.
curl \
-X GET \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
"https://defender-api.openzeppelin.com/sentinel/subscribers/{id}"
Update a Sentinel
To update a sentinel, you can call the update
function on the client. This will require the sentinel ID and a UpdateSentinelRequest
object as parameters:
await client.update('8181d9e0-88ce-4db0-802a-2b56e2e6a7b1', {name: 'My Updated Name', paused: true});
The subscribers/{id}
endpoint is used to a update existing sentinels via a PUT
request.
If you wish to call the API directly, you will need to construct a CreateBlockSubscriberRequest
object.
curl \
-X PUT \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
-d '{...}' \
"https://defender-api.openzeppelin.com/sentinel/subscribers/{id}"
Delete a Sentinel
You can delete a sentinel by ID. This will return a DeletedSentinelResponse
object.
await client.delete('8181d9e0-88ce-4db0-802a-2b56e2e6a7b1');
The subscribers/{id}
endpoint is used to a delete a sentinel via a DELETE
request.
curl \
-X DELETE \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H "X-Api-Key: $KEY" \
-H "Authorization: Bearer $TOKEN" \
"https://defender-api.openzeppelin.com/sentinel/subscribers/{id}"
An example response:
{
"message": "subscriber deleted"
}
Pause or unpause a Sentinel
You can pause and unpause a sentinel by ID. This will return a CreateSentinelResponse
object.
await client.pause('8181d9e0-88ce-4db0-802a-2b56e2e6a7b1');
await client.unpause('8181d9e0-88ce-4db0-802a-2b56e2e6a7b1');
If you wish to call the API directly, you can use the update
endpoint and set pause
to true
or false
accordingly.