Expand description
Sentry REST API documentation
This module includes the documentation for all routes of the Sentry
REST API and the corresponding requests, responses and parameters.
All routes are listed below. Here is an overview and links to all of them:
- Channel routes
- GET
/v5/channel/list
- GET
/v5/channel/:id/accounting
- GET
/v5/channel/:id/spender/:addr
(auth required) - POST
/v5/channel/:id/spender/:addr
(auth required) - GET
/v5/channel/:id/spender/all
(auth required) - GET
/v5/channel/:id/validator-messages
- GET
/v5/channel/:id/validator-messages/:addr
- GET
/v5/channel/:id/validator-messages/:addr/:validator_messages
- POST
/v5/channel/:id/validator-messages
(auth required) - GET
/v5/channel/:id/last-approved
- POST
/v5/channel/:id/pay
(auth required) - GET
/v5/channel/:id/get-leaf
- POST
/v5/channel/dummy-deposit
(auth required) available only with Dummy adapter
- GET
- Campaign routes
- GET
/v5/campaign/list
- POST
/v5/campaign
(auth required) - POST
/v5/campaign/:id
(auth required) - POST
/v5/campaign/:id/events
(auth required) - POST
/v5/campaign/:id/close
(auth required)
- GET
- Analytics routes
- GET
/v5/analytics
- GET
/v5/analytics/for-publisher
(auth required) - GET
/v5/analytics/for-advertiser
(auth required) - GET
/v5/analytics/for-admin
(auth required)
- GET
- GET
/cfg
Channel
All routes are implemented under the module channel.
Route parameters
Paths which include these parameters are validated as follows:
:id
-ChannelId
:addr
- a validAddress
orValidatorId
.
Routes
GET /v5/channel/list
The route is handled by channel::channel_list()
.
Request query parameters: ChannelListQuery
Response: ChannelListResponse
Examples
Query:
use primitives::{
sentry::channel_list::ChannelListQuery,
test_util::{IDS, LEADER},
ChainId,
};
fn main() {
// An empty query
{
let empty = "";
let empty_expected = ChannelListQuery {
page: 0,
validator: None,
chains: vec![],
};
assert_eq!(empty_expected, serde_qs::from_str(empty).unwrap());
}
// Query with `page`
{
let only_page = "page=14";
let only_page_expected = ChannelListQuery {
page: 14,
validator: None,
chains: vec![],
};
assert_eq!(only_page_expected, serde_qs::from_str(only_page).unwrap());
}
// Query with `validator`
{
let only_validator = "validator=0x80690751969B234697e9059e04ed72195c3507fa";
let only_validator_expected = ChannelListQuery {
page: 0,
validator: Some(IDS[&LEADER]),
chains: vec![],
};
assert_eq!(
only_validator_expected,
serde_qs::from_str(only_validator).unwrap()
);
}
// Query with `chains`
{
let chains_query = "chains[]=1&chains[]=1337";
let chains_expected = ChannelListQuery {
page: 0,
validator: None,
chains: vec![ChainId::new(1), ChainId::new(1337)],
};
assert_eq!(chains_expected, serde_qs::from_str(chains_query).unwrap());
}
// Query with all parameters
{
let all_query =
"page=14&validator=0x80690751969B234697e9059e04ed72195c3507fa&chains[]=1&chains[]=1337";
let all_expected = ChannelListQuery {
page: 14,
validator: Some(IDS[&LEADER]),
chains: vec![ChainId::new(1), ChainId::new(1337)],
};
assert_eq!(all_expected, serde_qs::from_str(all_query).unwrap());
}
}
GET /v5/channel/:id/accounting
Gets all of the accounting entries for a channel from the database and checks the balances.
The route is handled by channel::get_accounting_for_channel()
.
Response: AccountingResponse
Examples
Response:
use primitives::{balances::CheckedState, sentry::AccountingResponse};
use serde_json::{from_value, json};
fn main() {
// Empty balances
{
let json = json!({
"earners": {},
"spenders": {},
});
assert!(from_value::<AccountingResponse::<CheckedState>>(json).is_ok());
}
// Non-empty balances
{
// earners sum and spenders sum should always match since balances are CheckedState
let json = json!({
"earners": {
"0x80690751969B234697e9059e04ed72195c3507fa": "10000000000",
"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "20000000000",
"0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9": "30000000000",
},
"spenders": {
"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": "60000000000",
},
});
assert!(from_value::<AccountingResponse::<CheckedState>>(json).is_ok());
}
}
GET /v5/channel/:id/spender/:addr
(auth required)
Gets the spender limits for a spender on a Channel
. It does so by fetching the
latest Spendable entry from the database (or creating one if it doesn’t exist yet) from which
the total deposited amount is retrieved, and the latest NewState from which the total spent
amount is retrieved.
The route is handled by channel::get_spender_limits()
.
Response: SpenderResponse
Examples
Response:
use primitives::sentry::SpenderResponse;
use serde_json::{from_value, json};
fn main() {
let json = json!({
"spender": {
"totalDeposited": "10000000000",
"totalSpent": "100000000",
},
});
assert!(from_value::<SpenderResponse>(json).is_ok());
}
POST /v5/channel/:id/spender/:addr
(auth required)
This route forces the addition of a spender Accounting
(if one does not exist) to the given Channel
with spent = 0
.
This will also ensure that the spender is added to the NewState
as well.
The route is handled by channel::add_spender_leaf()
.
Response: SuccessResponse
GET /v5/channel/:id/spender/all
(auth required)
This routes gets total_deposited and total_spent for every spender on a Channel
The route is handled by channel::get_all_spender_limits()
.
Response: AllSpendersResponse
Examples
Response:
use primitives::sentry::AllSpendersResponse;
use serde_json::{from_value, json};
fn main() {
let json = json!({
"spenders": {
"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": {
"totalDeposited": "10000000000",
"totalSpent": "100000000",
},
"0xDd589B43793934EF6Ad266067A0d1D4896b0dff0": {
"totalDeposited": "90000000000",
"totalSpent": "20000000000",
},
"0x541b401362Ea1D489D322579552B099e801F3632": {
"totalDeposited": "1000000000",
"totalSpent": "1000000000",
},
},
"totalPages": 1,
"page": 0
});
assert!(from_value::<AllSpendersResponse>(json).is_ok());
}
GET /v5/channel/:id/validator-messages
Retrieve the latest validator MessageTypes
for a given Channel
.
The query limit
parameter is constraint to a maximum of Config.limits.msgs_find
,
if a large value is passed it will use the Config.limits.msgs_find
instead.
Sub-routes with additional filtering:
- GET
/v5/channel/:id/validator-messages/:addr
- filter by the givenValidatorId
- GET
/v5/channel/:id/validator-messages/:addr/:validator_messages
- filters by the givenValidatorId
and validatorMessageTypes
.-
:validator_messages
- url encoded list of ValidatorMessageTypes
separated by a+
.E.g.
NewState+ApproveState
becomesNewState%2BApproveState
-
The route is handled by channel::validator_message::list_validator_messages()
.
Request query parameters: ValidatorMessagesListQuery
Response: ValidatorMessagesListResponse
Examples
Query:
use primitives::sentry::validator_messages::ValidatorMessagesListQuery;
fn main() {
// Empty query - default values only
{
let empty_query = "";
let query: ValidatorMessagesListQuery = serde_qs::from_str(empty_query).unwrap();
assert_eq!(None, query.limit);
}
// Query with set limit
{
let query_str = "limit=200";
let query: ValidatorMessagesListQuery = serde_qs::from_str(query_str).unwrap();
assert_eq!(Some(200), query.limit);
}
}
Response:
use primitives::sentry::validator_messages::ValidatorMessagesListResponse;
use serde_json::{from_value, json};
fn main() {
// This response is generated using the Ethereum Adapter
let response_json = json!({
"messages": [
{
"from": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"received": "2022-08-09T14:45:43.110Z",
"msg": {
"type": "ApproveState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0xb2ce0010ad5867a4fb4acbde6525c261d76b592d290cb22af120573565168a2e49381e84d4f409c0989fa171dd687bf68b7eeff5b595c845cec8e9b8b1738dbd1c",
"isHealthy": true
}
},
{
"from": "0x80690751969B234697e9059e04ed72195c3507fa",
"received": "2022-08-09T14:45:38.090Z",
"msg": {
"type": "NewState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0x508bef21e91d5337ad71791503748fe0d7ee7592db90179be6f948570290d00b72e103b0d262452809ace183ebf83375072b4a359b6e441f2ad6f58b8552c8fa1b",
"earners": {
"0x80690751969B234697e9059e04ed72195c3507fa": "5",
"0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9": "100000",
"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "3"
},
"spenders": {
"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": "100008"
}
}
},
{
"from": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"received": "2022-08-09T14:45:28.160Z",
"msg": {
"type": "Heartbeat",
"signature": "0x3afda200de4ac36d5c8f1a53da0ffdca5077b556a53fb56bb9a79def1c06f972547b0099731b1ac9b4a26c183e2ea66b8cd1759cdc1513e3436d182e9592ae0e1b",
"stateRoot": "fa8f11b8aa6322905846f96219c855920b4449b18f0ceea97552e3880c5e4a9a",
"timestamp": "2022-08-09T14:45:28.121225273Z"
}
}
]
});
let response: ValidatorMessagesListResponse =
from_value(response_json).expect("Should deserialize");
println!("{response:#?}");
}
POST /v5/channel/:id/validator-messages
(auth required)
Create new validator MessageTypes
for the given Channel
,
used when propagating messages from the validator worker.
Authentication is required to validate that the Auth.uid
is either
the Channel.leader
or Channel.follower
.
The route is handled by channel::validator_message::create_validator_messages()
.
Request body (json): ValidatorMessagesCreateRequest
Response: SuccessResponse
Examples
Request:
use primitives::sentry::validator_messages::ValidatorMessagesCreateRequest;
use serde_json::{from_value, json};
fn main() {
// This request is generated using the Ethereum Adapter
let request_json = json!({
"messages": [
{
"type": "ApproveState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0xb2ce0010ad5867a4fb4acbde6525c261d76b592d290cb22af120573565168a2e49381e84d4f409c0989fa171dd687bf68b7eeff5b595c845cec8e9b8b1738dbd1c",
"isHealthy": true
},
{
"type": "NewState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0x508bef21e91d5337ad71791503748fe0d7ee7592db90179be6f948570290d00b72e103b0d262452809ace183ebf83375072b4a359b6e441f2ad6f58b8552c8fa1b",
"earners": {
"0x80690751969B234697e9059e04ed72195c3507fa": "5",
"0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9": "100000",
"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "3"
},
"spenders": {
"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": "100008"
}
},
{
"type": "Heartbeat",
"signature": "0x3afda200de4ac36d5c8f1a53da0ffdca5077b556a53fb56bb9a79def1c06f972547b0099731b1ac9b4a26c183e2ea66b8cd1759cdc1513e3436d182e9592ae0e1b",
"stateRoot": "fa8f11b8aa6322905846f96219c855920b4449b18f0ceea97552e3880c5e4a9a",
"timestamp": "2022-08-09T14:45:28.121225273Z"
}
]
});
let request: ValidatorMessagesCreateRequest =
from_value(request_json).expect("Should deserialize");
println!("{request:#?}");
}
GET /v5/channel/:id/last-approved
Retrieves the latest ApproveState
and the corresponding NewState
validator messages for the given Channel
.
If the Channel
is new one or both of the states might have not been generated yet.
The same is true of the Heartbeat
s messages if they are requested with the query parameter.
Retrieves the latest ApproveState
and the corresponding NewState
validator messages for the given Channel
.
If the Channel
is new one or both of the states might have not been generated yet.
The same is true of the Heartbeat
s messages if they are requested with the query parameter.
The route is handled by channel::last_approved()
.
Request query parameters: [LastApprovedQuery
][primitives::sentry::LastApprovedQuery]
Response: [LastApprovedResponse
][primitives::sentry::LastApprovedResponse]
Examples
Query:
use primitives::sentry::LastApprovedQuery;
fn main() {
// An empty query - no heartbeats will be included in the response (default).
{
// This is treated the same as `withHeartbeat=false` in the route.
let empty = "";
let empty_expected = LastApprovedQuery {
with_heartbeat: None,
};
assert_eq!(empty_expected, serde_qs::from_str(empty).unwrap());
}
// Query with `with_heartbeat` parameter - latest 2 Heartbeats from
// each Channel Validator will be returned in the response.
{
let with_heartbeat = "withHeartbeat=true";
let with_heartbeat_expected = LastApprovedQuery {
with_heartbeat: Some(true),
};
assert_eq!(
with_heartbeat_expected,
serde_qs::from_str(with_heartbeat).unwrap()
);
}
}
Response:
use primitives::{balances::UncheckedState, sentry::LastApprovedResponse};
use serde_json::{from_value, json};
fn main() {
// An empty response - Channel is brand new and neither an NewState nor a ApproveState
// have been generated yet.
// This is threated the same as `with_heartbeat=false` in the route.
{
let empty_json = json!({});
let empty_expected = LastApprovedResponse::<UncheckedState> {
last_approved: None,
heartbeats: None,
};
assert_eq!(
empty_expected,
from_value(empty_json).expect("Should deserialize")
);
}
// Response without `with_heartbeat=true` query parameter
// or with a `with_heartbeat=false` query parameter
{
let response_json = json!({
"lastApproved": {
"newState": {
"from": "0x80690751969B234697e9059e04ed72195c3507fa",
"received": "2022-08-09T14:45:38.090Z",
"msg": {
"type": "NewState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0x508bef21e91d5337ad71791503748fe0d7ee7592db90179be6f948570290d00b72e103b0d262452809ace183ebf83375072b4a359b6e441f2ad6f58b8552c8fa1b",
"earners": {
"0x80690751969B234697e9059e04ed72195c3507fa": "5",
"0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9": "100000",
"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "3"
},
"spenders": {
"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": "100008"
}
}
},
"approveState": {
"from": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"received": "2022-08-09T14:45:43.110Z",
"msg": {
"type": "ApproveState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0xb2ce0010ad5867a4fb4acbde6525c261d76b592d290cb22af120573565168a2e49381e84d4f409c0989fa171dd687bf68b7eeff5b595c845cec8e9b8b1738dbd1c",
"isHealthy": true
}
}
}
});
let response: LastApprovedResponse<UncheckedState> =
from_value(response_json).expect("Should deserialize");
assert!(response.heartbeats.is_none());
}
{
let response_json = json!({
"lastApproved": {
"newState": {
"from": "0x80690751969B234697e9059e04ed72195c3507fa",
"received": "2022-08-09T14:45:38.090Z",
"msg": {
"type": "NewState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0x508bef21e91d5337ad71791503748fe0d7ee7592db90179be6f948570290d00b72e103b0d262452809ace183ebf83375072b4a359b6e441f2ad6f58b8552c8fa1b",
"earners": {
"0x80690751969B234697e9059e04ed72195c3507fa": "5",
"0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9": "100000",
"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "3"
},
"spenders": {
"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": "100008"
}
}
},
"approveState": {
"from": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"received": "2022-08-09T14:45:43.110Z",
"msg": {
"type": "ApproveState",
"stateRoot": "a1e2f6ee08185ae06e3212e56ad1e0fcbae95ac8939871eb96e1ee3016234321",
"signature": "0xb2ce0010ad5867a4fb4acbde6525c261d76b592d290cb22af120573565168a2e49381e84d4f409c0989fa171dd687bf68b7eeff5b595c845cec8e9b8b1738dbd1c",
"isHealthy": true
}
}
},
"heartbeats": [
{
"from": "0x80690751969B234697e9059e04ed72195c3507fa",
"received": "2022-08-09T14:45:58.110Z",
"msg": {
"type": "Heartbeat",
"signature": "0xc96ed701fc53e27cb28b323c26060273a5bdbad07c4b5470d8090c6a8b03954422b5d0dcc791cc47b5e4186045f60971d7e8e4d69b380cf250fe416cf4aac6901b",
"stateRoot": "888f915d6b84ccfa80d3cc6536021efab05f63293ddfb66f1bb1c191909d1372",
"timestamp": "2022-08-09T14:45:58.097376454Z"
}
},
{
"from": "0x80690751969B234697e9059e04ed72195c3507fa",
"received": "2022-08-09T14:45:28.090Z",
"msg": {
"type": "Heartbeat",
"signature": "0x3f780e2abe0d704428a7921c2f18c070ad503953ef248f533b4ad13fa97c239c5e43a9f3db5077b24d4912cb13367337dc4a6c26976a15811a728e316e7275c41c",
"stateRoot": "799c72322f9c35840c4bf41045df2623b33a97a5dfa3994022389ddf8930aac6",
"timestamp": "2022-08-09T14:45:28.084928288Z"
}
},
{
"from": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"received": "2022-08-09T14:46:03.170Z",
"msg": {
"type": "Heartbeat",
"signature": "0xd4ce3c8e1cc8ab690cddc2a6cd229311e91e13a53e59b1ec80d8f877afd241af2c86c5fade37be5057d36c8fc5e69d3222b49b98bf686ee00e73005cc280ebc41b",
"stateRoot": "d0dc740d3352cdd7da9b823aa4051830f9757fae66c553a88176ce0001e378fb",
"timestamp": "2022-08-09T14:46:03.127887835Z"
}
},
{
"from": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"received": "2022-08-09T14:45:28.160Z",
"msg": {
"type": "Heartbeat",
"signature": "0x3afda200de4ac36d5c8f1a53da0ffdca5077b556a53fb56bb9a79def1c06f972547b0099731b1ac9b4a26c183e2ea66b8cd1759cdc1513e3436d182e9592ae0e1b",
"stateRoot": "fa8f11b8aa6322905846f96219c855920b4449b18f0ceea97552e3880c5e4a9a",
"timestamp": "2022-08-09T14:45:28.121225273Z"
}
}
]
});
let response: LastApprovedResponse<UncheckedState> =
from_value(response_json).expect("Should deserialize");
assert!(response.heartbeats.is_some());
}
}
POST /v5/channel/:id/pay
(auth required)
Channel Payout with authentication of the spender.
This route handles withdrawals of advertiser funds for the authenticated spender. It needs to ensure all campaigns are closed. It accepts a JSON body in the request which contains all of the earners and updates their balances accordingly. Used when an advertiser/spender wants to get their remaining funds back.
The route is handled by channel::channel_payout()
.
Request JSON body: ChannelPayRequest
Response: SuccessResponse
Examples
Request (json):
use primitives::sentry::ChannelPayRequest;
use serde_json::json;
fn main() {
let channel_pay_json = json!({
"payouts": {
"0x80690751969B234697e9059e04ed72195c3507fa": "10000000000",
"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "20000000000",
"0x0e880972A4b216906F05D67EeaaF55d16B5EE4F1": "30000000000",
},
});
let channel_pay_json = serde_json::to_string(&channel_pay_json).expect("should serialize");
assert!(serde_json::from_str::<ChannelPayRequest>(&channel_pay_json).is_ok());
}
GET `/v5/channel/:id/get-leaf
This route gets the latest approved state (NewState
/ApproveState
pair),
finds the given spender
or earner
in the balances tree and produces a Merkle proof for it.
This is useful for the Platform to verify if a spender leaf really exists.
The route is handled by channel::get_leaf()
.
Response: GetLeafResponse
Routes:
- GET
/v5/channel/:id/get-leaf/spender/:addr
- GET
/v5/channel/:id/get-leaf/earner/:addr
Examples:
URI for retrieving leaf of a Spender:
/v5/channel/0xf147fa3f1c5e5e06d359c15aa082442cc3e0380f306306022d1e9047c565a0f9/get-leaf/spender/0xDd589B43793934EF6Ad266067A0d1D4896b0dff0
URI for retrieving leaf of an Earner:
/v5/channel/0xf147fa3f1c5e5e06d359c15aa082442cc3e0380f306306022d1e9047c565a0f9/get-leaf/earner/0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9
Response:
use primitives::sentry::GetLeafResponse;
use serde_json::{from_value, json};
fn main() {
let json = json!({
"merkleProof": "8ea7760ca2dbbe00673372afbf8b05048717ce8a305f1f853afac8c244182e0c",
});
assert!(from_value::<GetLeafResponse>(json).is_ok());
}
POST /v5/channel/dummy-deposit
(auth required)
Set a deposit for a Channel and depositor (the authenticated address) in the Dummy adapter.
NOTE: This route is available only when using the Dummy adapter and it’s not an official production route!
The route is handled by channel::channel_dummy_deposit()
.
Request body (json): ChannelDummyDeposit
Response: SuccessResponse
Examples
Request:
use primitives::{unified_num::FromWhole, UnifiedNum};
use sentry::routes::channel::ChannelDummyDeposit;
use serde_json::{from_value, json};
fn main() {
let request_json = json!({
"channel": {
"leader": "0x80690751969B234697e9059e04ed72195c3507fa",
"follower": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"guardian": "0xe061E1EB461EaBE512759aa18A201B20Fe90631D",
"token": "0x2bcaf6968aec8a3b5126fbfab5fd419da6e8ad8e",
"nonce": "0"
},
"deposit": {
"total": "20000000000000"
}
});
let request: ChannelDummyDeposit = from_value(request_json).expect("Should deserialize");
assert_eq!(UnifiedNum::from_whole(200000.0), request.deposit.total);
println!("{request:#?}");
}
Campaign
All routes are implemented under the module campaign.
Route parameters
Paths which include these parameters are validated as follow:
:id
-CampaignId
Routes
GET /v5/campaign/list
Lists all campaigns with pagination and orders them in
ascending order (ASC
) by Campaign.created
.
This ensures that the order in the pages will not change if a new
Campaign
is created while still retrieving a page.
The route is handled by campaign::campaign_list()
.
Request query parameters: CampaignListQuery
page=[integer]
(optional) default:0
creator=[0x....]
(optional) - address of the creator to be filtered byactiveTo=[integer]
(optional) in seconds - filters campaigns byCampaign.active.to > query.activeTo
validator=[0x...]
orleader=[0x...]
(optional) - address of the validator to be filtered by. You can eithervalidator=[0x...]
- it will return allCampaign
s where this address is eitherChannel.leader
orChannel.follower
leader=[0x...]
- it will return allCampaign
s where this address isChannel.leader
Response: CampaignListResponse
Examples
Query:
use chrono::{TimeZone, Utc};
use primitives::{
sentry::campaign_list::{CampaignListQuery, ValidatorParam},
test_util::{ADVERTISER, FOLLOWER, IDS, LEADER},
};
fn main() {
// Empty query - default values only
{
let empty_query = "";
let query: CampaignListQuery = serde_qs::from_str(empty_query).unwrap();
assert_eq!(0, query.page);
assert!(
Utc::now() >= query.active_to_ge,
"By default `activeTo` is set to `Utc::now()`"
);
assert!(query.creator.is_none());
assert!(query.validator.is_none());
}
// In the following examples we always use `activeTo`
// as it makes simpler examples for assertions rather than using the default `Utc::now()`
// Query with `activeTo` only
{
let active_to_query = "activeTo=1624192200";
let active_to = CampaignListQuery {
page: 0,
active_to_ge: Utc.ymd(2021, 6, 20).and_hms(12, 30, 0),
creator: None,
validator: None,
};
assert_eq!(active_to, serde_qs::from_str(active_to_query).unwrap());
}
// Query with `page` & `activeTo`
{
let with_page_query = "page=14&activeTo=1624192200";
let with_page = CampaignListQuery {
page: 14,
active_to_ge: Utc.ymd(2021, 6, 20).and_hms(12, 30, 0),
creator: None,
validator: None,
};
assert_eq!(with_page, serde_qs::from_str(with_page_query).unwrap());
}
// Query with `creator`
{
let with_creator_query =
"activeTo=1624192200&creator=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0";
let with_creator = CampaignListQuery {
page: 0,
active_to_ge: Utc.ymd(2021, 6, 20).and_hms(12, 30, 0),
creator: Some(*ADVERTISER),
validator: None,
};
assert_eq!(
with_creator,
serde_qs::from_str(with_creator_query).unwrap()
);
}
// Query with `validator`
// You can either have `leader` or `validator` but not both!
{
let with_creator_validator_query =
"activeTo=1624192200&validator=0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7";
let with_creator_validator = CampaignListQuery {
page: 0,
active_to_ge: Utc.ymd(2021, 6, 20).and_hms(12, 30, 0),
creator: None,
validator: Some(ValidatorParam::Validator(IDS[&FOLLOWER])),
};
assert_eq!(
with_creator_validator,
serde_qs::from_str(with_creator_validator_query).unwrap()
);
}
// Query with `leader`
// You can either have `leader` or `validator` but not both!
{
let with_leader_query =
"activeTo=1624192200&leader=0x80690751969B234697e9059e04ed72195c3507fa";
let with_leader = CampaignListQuery {
page: 0,
active_to_ge: Utc.ymd(2021, 6, 20).and_hms(12, 30, 0),
creator: None,
validator: Some(ValidatorParam::Leader(IDS[&LEADER])),
};
assert_eq!(with_leader, serde_qs::from_str(with_leader_query).unwrap());
}
// Query with all parameters and `validator`
// You can either have `leader` or `validator` but not both!
{
let full_query = "page=14&activeTo=1624192200&creator=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0&validator=0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7";
let full_expected = CampaignListQuery {
page: 14,
active_to_ge: Utc.ymd(2021, 6, 20).and_hms(12, 30, 0),
creator: Some(*ADVERTISER),
validator: Some(ValidatorParam::Validator(IDS[&FOLLOWER])),
};
assert_eq!(full_expected, serde_qs::from_str(full_query).unwrap());
}
}
Response:
use primitives::sentry::campaign_list::CampaignListResponse;
use serde_json::{from_value, json};
fn main() {
let json = json!({
"campaigns": [
{
"id": "0x936da01f9abd4d9d80c702af85c822a8",
"channel": {
"leader": "0x80690751969B234697e9059e04ed72195c3507fa",
"follower": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"guardian": "0xe061E1EB461EaBE512759aa18A201B20Fe90631D",
"token": "0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E",
"nonce": "0"
},
"creator": "0xDd589B43793934EF6Ad266067A0d1D4896b0dff0",
"budget": "15000000000",
"validators": [
{
"id": "0x80690751969B234697e9059e04ed72195c3507fa",
"fee": "500000000",
"url": "http://localhost:8005/"
},
{
"id": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"fee": "400000000",
"url": "http://localhost:8006/"
}
],
"title": "Dummy Campaign",
"pricingBounds": {
"CLICK": {
"min": "6000",
"max": "10000"
},
"IMPRESSION": {
"min": "4000",
"max": "5000"
}
},
"eventSubmission": {
"allow": []
},
"adUnits": [
{
"ipfs": "Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f",
"type": "legacy_250x250",
"mediaUrl": "ipfs://QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR",
"mediaMime": "image/jpeg",
"targetUrl": "https://www.adex.network/?stremio-test-banner-1",
"owner": "0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9",
"created": 1564390800000_u64,
"title": "Dummy AdUnit 1",
"description": "Dummy AdUnit description 1",
"archived": false
},
{
"ipfs": "QmVhRDGXoM3Fg3HZD5xwMuxtb9ZErwC8wHt8CjsfxaiUbZ",
"type": "legacy_250x250",
"mediaUrl": "ipfs://QmQB7uz7Gxfy7wqAnrnBcZFaVJLos8J9gn8mRcHQU6dAi1",
"mediaMime": "image/jpeg",
"targetUrl": "https://www.adex.network/?adex-campaign=true&pub=stremio",
"owner": "0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9",
"created": 1564390800000_u64,
"title": "Dummy AdUnit 2",
"description": "Dummy AdUnit description 2",
"archived": false
}
],
"targetingRules": [],
"created": 1612162800000_u64,
"activeTo": 4073414400000_u64
},
{
"id": "0x127b98248f4e4b73af409d10f62daeaa",
"channel": {
"leader": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"follower": "0x80690751969B234697e9059e04ed72195c3507fa",
"guardian": "0x79D358a3194d737880B3eFD94ADccD246af9F535",
"token": "0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E",
"nonce": "0"
},
"creator": "0xDd589B43793934EF6Ad266067A0d1D4896b0dff0",
"budget": "2000000000",
"validators": [
{
"id": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"fee": "10000000",
"url": "http://localhost:8006/"
},
{
"id": "0x80690751969B234697e9059e04ed72195c3507fa",
"fee": "5000000",
"url": "http://localhost:8005/"
}
],
"title": "Dummy Campaign 2 in Chain #1337",
"pricingBounds": {
"CLICK": {
"min": "300000",
"max": "500000"
},
"IMPRESSION": {
"min": "100000",
"max": "200000"
}
},
"eventSubmission": {
"allow": []
},
"adUnits": [
{
"ipfs": "Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f",
"type": "legacy_250x250",
"mediaUrl": "ipfs://QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR",
"mediaMime": "image/jpeg",
"targetUrl": "https://www.adex.network/?stremio-test-banner-1",
"owner": "0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9",
"created": 1564390800000_u64,
"title": "Dummy AdUnit 1",
"description": "Dummy AdUnit description 1",
"archived": false
},
{
"ipfs": "QmVhRDGXoM3Fg3HZD5xwMuxtb9ZErwC8wHt8CjsfxaiUbZ",
"type": "legacy_250x250",
"mediaUrl": "ipfs://QmQB7uz7Gxfy7wqAnrnBcZFaVJLos8J9gn8mRcHQU6dAi1",
"mediaMime": "image/jpeg",
"targetUrl": "https://www.adex.network/?adex-campaign=true&pub=stremio",
"owner": "0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9",
"created": 1564390800000_u64,
"title": "Dummy AdUnit 2",
"description": "Dummy AdUnit description 2",
"archived": false
}
],
"targetingRules": [],
"created": 1612162800000_u64,
"activeFrom": 1656280800000_u64,
"activeTo": 4073414400000_u64
},
{
"id": "0xa78f3492481b41a688488a7aa1ff17df",
"channel": {
"leader": "0x80690751969B234697e9059e04ed72195c3507fa",
"follower": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"guardian": "0x79D358a3194d737880B3eFD94ADccD246af9F535",
"token": "0x12a28f2bfBFfDf5842657235cC058242f40fDEa6",
"nonce": "1"
},
"creator": "0x541b401362Ea1D489D322579552B099e801F3632",
"budget": "2000000000",
"validators": [
{
"id": "0x80690751969B234697e9059e04ed72195c3507fa",
"fee": "200000000",
"url": "http://localhost:8005/"
},
{
"id": "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"fee": "175000000",
"url": "http://localhost:8006/"
}
],
"title": "Dummy Campaign 3 in Chain #1",
"pricingBounds": {
"CLICK": {
"min": "3500",
"max": "6500"
},
"IMPRESSION": {
"min": "1500",
"max": "2500"
}
},
"eventSubmission": {
"allow": []
},
"adUnits": [
{
"ipfs": "QmYwcpMjmqJfo9ot1jGe9rfXsszFV1WbEA59QS7dEVHfJi",
"type": "legacy_250x250",
"mediaUrl": "ipfs://QmQB7uz7Gxfy7wqAnrnBcZFaVJLos8J9gn8mRcHQU6dAi1",
"mediaMime": "image/jpeg",
"targetUrl": "https://www.adex.network/?adex-campaign=true",
"owner": "0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9",
"created": 1564390800000_u64,
"title": "Dummy AdUnit 3",
"description": "Dummy AdUnit description 3",
"archived": false
},
{
"ipfs": "QmTAF3FsFDS7Ru8WChoD9ofiHTH8gAQfR4mYSnwxqTDpJH",
"type": "legacy_250x250",
"mediaUrl": "ipfs://QmQAcfBJpDDuH99A4p3pFtUmQwamS8UYStP5HxHC7bgYXY",
"mediaMime": "image/jpeg",
"targetUrl": "https://adex.network",
"owner": "0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9",
"created": 1564390800000_u64,
"title": "Dummy AdUnit 4",
"description": "Dummy AdUnit description 4",
"archived": false
}
],
"targetingRules": [],
"created": 1612162800000_u64,
"activeTo": 4073414400000_u64
}
],
"totalPages": 1,
"page": 0
});
assert!(from_value::<CampaignListResponse>(json).is_ok());
}
POST /v5/campaign
(auth required)
Create a new Campaign. Request must be sent by the Campaign.creator
.
Authentication is required to validate Campaign.creator
== Auth.uid
It will make sure the Channel
is created if new and it will update
the spendable amount using the Adapter.get_deposit()
.
The route is handled by campaign::create_campaign()
.
Request body (json): [CreateCampaign
][primitives::sentry::campaign_create::CreateCampaign]
Response: Campaign
Examples
use primitives::{sentry::campaign_create::CreateCampaign, test_util::DUMMY_CAMPAIGN, CampaignId};
use serde_json::json;
use std::str::FromStr;
fn main() {
// CreateCampaign in an HTTP request.
// A CampaignId will be randomly generated for the newly created Campaign.
{
let create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None);
let _create_campaign_str =
serde_json::to_string(&create_campaign).expect("should serialize");
let create_campaign_json = json!({
"channel":{
"leader":"0x80690751969B234697e9059e04ed72195c3507fa",
"follower":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"guardian":"0xe061E1EB461EaBE512759aa18A201B20Fe90631D",
"token":"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E",
"nonce":"987654321"
},
"creator":"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F",
"budget":"100000000000",
"validators":[
{
"id":"0x80690751969B234697e9059e04ed72195c3507fa",
"fee":"3000",
"url":"http://localhost:8005"
},
{
"id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"fee":"2000",
"url":"http://localhost:8006"
}
],
"title":"Dummy Campaign",
"pricingBounds":{
"CLICK":{"min":"0","max":"0"},
"IMPRESSION":{"min":"1","max":"10"}
},
"eventSubmission":{"allow":[]},
"targetingRules":[],
"created":1612162800000_u64,
"activeTo":4073414400000_u64
});
let create_campaign_json =
serde_json::to_string(&create_campaign_json).expect("should serialize");
let deserialized: CreateCampaign =
serde_json::from_str(&create_campaign_json).expect("should deserialize");
assert_eq!(create_campaign, deserialized);
}
// CreateCampaign with a provided ID
{
let mut create_campaign =
CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None);
create_campaign.id = Some(
CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("Should be valid id"),
);
let create_campaign_json = json!({
"id":"0x936da01f9abd4d9d80c702af85c822a8",
"channel":{
"leader":"0x80690751969B234697e9059e04ed72195c3507fa",
"follower":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"guardian":"0xe061E1EB461EaBE512759aa18A201B20Fe90631D",
"token":"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E",
"nonce":"987654321"
},
"creator":"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F",
"budget":"100000000000",
"validators":[
{
"id":"0x80690751969B234697e9059e04ed72195c3507fa",
"fee":"3000",
"url":"http://localhost:8005"
},
{
"id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"fee":"2000",
"url":"http://localhost:8006"
}
],
"title":"Dummy Campaign",
"pricingBounds":{"CLICK":{"min":"0","max":"0"},"IMPRESSION":{"min":"1","max":"10"}},
"eventSubmission":{"allow":[]},
"targetingRules":[],
"created":1612162800000_u64,
"activeTo":4073414400000_u64
});
let create_campaign_json =
serde_json::to_string(&create_campaign_json).expect("should serialize");
let deserialized: CreateCampaign =
serde_json::from_str(&create_campaign_json).expect("should deserialize");
assert_eq!(create_campaign, deserialized);
}
}
POST /v5/campaign/:id
(auth required)
Modify the Campaign
. Request must be sent by the Campaign.creator
.
Authentication is required to validate Campaign.creator
== Auth.uid
The route is handled by campaign::update_campaign::handle_route()
.
Request body (json): [ModifyCampaign
][primitives::sentry::campaign_modify::ModifyCampaign]
Response: Campaign
Examples
use primitives::{sentry::campaign_modify::ModifyCampaign, unified_num::FromWhole, UnifiedNum};
use serde_json::json;
fn main() {
{
let modify_campaign = ModifyCampaign {
ad_units: None,
budget: Some(UnifiedNum::from_whole(100)),
validators: None,
title: None,
pricing_bounds: None,
event_submission: None,
targeting_rules: None,
};
let modify_campaign_json = json!({
"ad_units": null,
"budget": "10000000000",
"validators": null,
"title": null,
"pricing_bounds": null,
"event_submission": null,
"targeting_rules": null,
});
let modify_campaign_json =
serde_json::to_string(&modify_campaign_json).expect("should serialize");
let deserialized: ModifyCampaign =
serde_json::from_str(&modify_campaign_json).expect("should deserialize");
assert_eq!(modify_campaign, deserialized);
}
}
POST /v5/campaign/:id/events
Add new Event
s (IMPRESSION
s & CLICK
s) to the Campaign
.
Applies Campaign.event_submission
rules and additional validation using check_access()
.
The route is handled by campaign::insert_events::handle_route()
.
Request body (json): InsertEventsRequest
Response: SuccessResponse
POST /v5/campaign/:id/close
(auth required)
Close the campaign.
The route is handled by campaign::close_campaign()
.
Request must be sent by the Campaign.creator
.
Authentication is required to validate Campaign.creator
== Auth.uid
Closes the campaign by setting Campaign.budget
so that remaining budget = 0
.
Response: SuccessResponse
Analytics
GET /v5/analytics
Allowed keys: [AllowedKey::Country
][primitives::analytics::query::AllowedKey::Country], [AllowedKey::AdSlotType
][primitives::analytics::query::AllowedKey::AdSlotType]
Request query parameters: AnalyticsQuery
Response: AnalyticsResponse
Examples
Query:
use primitives::{
analytics::{
query::{AllowedKey, Time},
AnalyticsQuery, Metric, OperatingSystem, Timeframe,
},
sentry::{DateHour, EventType},
Address, CampaignId, ChainId, IPFS,
};
use std::str::FromStr;
fn main() {
// Empty query - default values only
{
let empty_query = "";
let query: AnalyticsQuery = serde_qs::from_str(empty_query).unwrap();
assert_eq!(100, query.limit);
assert_eq!(EventType::Impression, query.event_type);
assert!(matches!(query.metric, Metric::Count));
assert!(matches!(query.time.timeframe, Timeframe::Day));
}
// Query with different metric/chain/eventType
{
let query_str = "limit=200&eventType=CLICK&metric=paid&timeframe=month";
let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap();
assert_eq!(200, query.limit);
assert_eq!(EventType::Click, query.event_type);
assert!(matches!(query.metric, Metric::Paid));
assert!(matches!(query.time.timeframe, Timeframe::Month));
}
// Query with allowed keys for guest - country, slotType
{
let query_str = "country=Bulgaria&adSlotType=legacy_300x100";
let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap();
assert_eq!(Some("Bulgaria".to_string()), query.country);
assert_eq!(Some("legacy_300x100".to_string()), query.ad_slot_type);
}
// Query with all possible fields (publisher/advertiser/admin)
{
let query_str = "limit=200&eventType=CLICK&metric=paid&segmentBy=country\
&timeframe=week&start=2022-08-04+09:00:00.000000000+UTC\
&campaignId=0x936da01f9abd4d9d80c702af85c822a8\
&adUnit=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR\
&adSlot=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f\
&adSlotType=legacy_300x100\
&advertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0\
&publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9\
&hostname=localhost&country=Bulgaria&osName=Windows\
&chains[0]=1&chains[1]=1337";
let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap();
assert_eq!(query.limit, 200);
assert_eq!(query.event_type, EventType::Click);
assert!(matches!(query.metric, Metric::Paid));
assert_eq!(query.segment_by, Some(AllowedKey::Country));
assert_eq!(
query.time,
Time {
timeframe: Timeframe::Week,
start: DateHour::from_ymdh(2022, 8, 4, 9),
end: None,
}
);
assert_eq!(
query.campaign_id,
Some(
CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8")
.expect("should be valid")
)
);
assert_eq!(
query.ad_unit,
Some(
IPFS::from_str("QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR")
.expect("should be valid")
)
);
assert_eq!(
query.ad_slot,
Some(
IPFS::from_str("Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f")
.expect("should be valid")
)
);
assert_eq!(query.ad_slot_type, Some("legacy_300x100".to_string()));
assert_eq!(
query.advertiser,
Some(
Address::from_str("0xDd589B43793934EF6Ad266067A0d1D4896b0dff0")
.expect("should be valid")
)
);
assert_eq!(
query.publisher,
Some(
Address::from_str("0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9")
.expect("should be valid")
)
);
assert_eq!(query.hostname, Some("localhost".to_string()));
assert_eq!(query.country, Some("Bulgaria".to_string()));
assert_eq!(
query.os_name,
Some(OperatingSystem::Whitelisted("Windows".to_string()))
);
assert_eq!(query.chains, vec!(ChainId::new(1), ChainId::new(1337)));
}
}
Response:
use primitives::sentry::AnalyticsResponse;
use serde_json::{from_value, json};
fn main() {
let json = json!({
"analytics": [{
"time": 1659592800,
"value": "3",
"segment": null
},
{
"time": 1659592800,
"value": "10000000000",
"segment": null
},
{
"time": 1659592800,
"value": "100000000",
"segment": "country"
}]
});
assert!(from_value::<AnalyticsResponse>(json).is_ok());
}
GET /v5/analytics/for-publisher
(auth required)
Returns all analytics where the currently authenticated address Auth.uid
is a publisher.
All ALLOWED_KEYS
are allowed for this route.
The route is handled by get_analytics()
.
Request query parameters: AnalyticsQuery
Response: AnalyticsResponse
Examples
GET /v5/analytics/for-advertiser
(auth required)
Returns all analytics where the currently authenticated address Auth.uid
is an advertiser.
All ALLOWED_KEYS
are allowed for this route.
The route is handled by get_analytics()
.
Request query parameters: AnalyticsQuery
Response: AnalyticsResponse
Examples
GET /v5/analytics/for-admin
(auth required)
Admin access to the analytics with no restrictions on the keys for filtering.
All ALLOWED_KEYS
are allowed for admins.
Admin addresses are configured in the Config.sentry.admins
.
The route is handled by get_analytics()
.
Request query parameters: AnalyticsQuery
Response: AnalyticsResponse
Examples
GET /cfg
Gets the config that the validator is running on.
The route is handled by get_cfg()
Response: Config
Examples
Response:
use primitives::{config::GANACHE_CONFIG, Config};
use serde_json::{from_value, json};
fn main() {
let json = json!({
"creators_whitelist": [
"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F",
"0xDd589B43793934EF6Ad266067A0d1D4896b0dff0",
"0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9",
"0x541b401362Ea1D489D322579552B099e801F3632"
],
"validators_whitelist": [
"0x80690751969B234697e9059e04ed72195c3507fa",
"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
"0x6B83e7D6B72c098d48968441e0d05658dc17Adb9"
],
"chain": {
"Ganache #1337": {
"chain_id": 1337,
"rpc": "http://localhost:1337/",
"outpace": "0xAbc27d46a458E2e49DaBfEf45ca74dEDBAc3DD06",
"token": {
"Mocked TOKEN 1337": {
"min_campaign_budget": "1000000000000000000",
"min_validator_fee": "1000000000000",
"precision": 18,
"address": "0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E"
}
}
},
"Ganache #1": {
"chain_id": 1,
"rpc": "http://localhost:8545/",
"outpace": "0x26CBc2eAAe377f6Ac4b73a982CD1125eF4CEC96f",
"token": {
"Mocked TOKEN 1": {
"min_campaign_budget": "1000000000000000000",
"min_validator_fee": "1000000000000",
"precision": 18,
"address": "0x12a28f2bfBFfDf5842657235cC058242f40fDEa6"
}
}
}
},
"limits": {
"units_for_slot": {
"max_campaigns_earning_from": 25,
"global_min_impression_price": "10"
},
"channels_find": 200,
"campaigns_find": 200,
"spendable_find": 200,
"msgs_find": 10,
"analytics_find": 5000,
"ip_rate_limit": {
"type": "ip",
"timeframe": 1200000
}
},
"sentry": {
"analytics_maxtime": 20000,
"admins": [
"0x80690751969B234697e9059e04ed72195c3507fa"
],
"platform": {
"url": "https://platform.adex.network/",
"keep_alive_interval": 1200000
},
},
"worker": {
"max_channels": 512,
"wait_time": 500,
"heartbeat_time": 30000,
"health_threshold_promilles": 950,
"health_unsignable_promilles": 750,
"timeouts": {
"propagation": 2000,
"fetch": 5000,
"all_campaigns": 5000,
"channel_tick": 8000,
}
}
});
assert_eq!(
from_value::<Config>(json).expect("Should deserialize"),
GANACHE_CONFIG.clone()
);
}
Modules
/v5/campaign
routes/v5/channel
routesFunctions
/v5/analytics
routes
Request query parameters: [AnalyticsQuery
]./cfg
request