The Gas Station Server includes an Access Controller mechanism to manage access to the /execute_tx endpoint. This feature allows you to implement filtering logic based on properties derived from transactions. Currently, the Access Controller supports filtering based on the sender's address, enabling you to block or allow specific addresses.
| parameter | mandatory | possible values |
|---|---|---|
sender-address |
yes | '0x0000...', [0x0000.., 0x1111...], '*' |
gas-budget |
no | '=100', '<100', '<=100', '>100', '>=100', '!=100' |
move-call-package-address |
no | '0x0000...', [0x0000..., 0x1111...], '*' |
ptb-command-count |
no | '=10', '<10', '<=10', '>10', '>=10', '!=10' |
action |
yes | 'allow', 'deny', Hook Server URL |
gas_usage |
no | See Gas Usage Filter |
rego_expression |
no | See Gas Rego Expression |
-
Disable All Requests and Allow Only a Specific Address
The following configuration denies all incoming transactions except for move calls to package (
0x0202....) originating from the specified sender address (0x0101....):access-controller: access-policy: deny-all rules: - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101" move-call-package-address: "0x0202020202020202020202020202020202020202020202020202020202020202" action: allow # allowed actions: 'allow', 'deny', a hook url (see "Hook Server" section)
-
Enables All Requests and Deny Only a Specific Address
The following configuration allows all incoming transactions except those from the specified sender address (
0x0101...):access-controller: access-policy: deny-all rules: - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101" action: deny
-
Gas Budget Constraints
The following configuration denies all incoming transactions except those from the specified sender address (
0x0101...) and the transaction gas budget below the limit1000000access-controller: access-policy: deny-all rules: - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101" transaction-gas-budget: <1000000 # allowed operators: =, !=, <, >, <=, >= action: allow
-
Advanced budgeting management
The following configuration accept all incoming transactions with gas budget below
500000. For address sender address (0x0101...) the allowed gas budget is increased to1000000access-controller: access-policy: deny-all rules: - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101" transaction-gas-budget: <=10000000 action: allow - sender-address: '*' transaction-gas-budget: <500000 action: allow
-
Programmable Transaction Command Count Limits
To avoid users submitting transactions blocks with a large number of transactions, limits for the commands in the programmable transaction can be configured. In the following example, the sender may only submit up to one command in the programmable transaction.
Note that this rule condition is only applied to transactions, that include a programmable transaction and will be ignored for other transaction kinds.
access-controller: access-policy: deny-all rules: - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101" ptb-command-count: <=1 # allowed operators: =, !=, <, >, <=, >= action: allow
The Rego Expression Filter allows you to evaluate incoming transaction payloads against custom logic by using the Rego language. This gives you the flexibility to check properties like the sender address or any other field available in the transaction data.
Below is an example JSON payload against which a Rego rule is evaluated:
{
"transaction_data": {
"V1": {
"kind": {
"ProgrammableTransaction": {
"inputs": [
{
"Pure": [
5,
104,
101,
108,
108,
111
]
}
],
"commands": [
{
"MoveCall": {
"package": "0xb674e2ed79db3c25fa4c00d5c7d62a9c18089e1fc4c2de5b5ee8b2836a85ae26",
"module": "allowed_module_name",
"function": "allowed_module_function",
"type_arguments": [],
"arguments": [
{
"Input": 0
}
]
}
}
]
}
},
"sender": "0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311",
"gas_data": {
"payment": [
[
"0x1369ab7cca1c229d093060d66666db7c0db1edf43310d5e56acec2dafa492ffb",
9521034,
"9knpRtwCc9LK1BCJ222KZwgr9ZVHDyZhiLUTPt82FtmV"
],
[
"0x1932fbdf314cb263f4d2a00656144e0951edd99fe5489b07b799b7087f8de20d",
9521055,
"BAubkJuEwD1PRGL1CLBRjv3GCP83p93J7ZAdSsdVtifk"
],
[
"0x19d732e1b95c8b6af724ba026f8f61e4b72b9931777a0e1d0b8d3054de1b2ef4",
9521050,
"GRyLJcRSepVk3r9Fjdmv9MxauX6mrzYVptAhPwzVRu61"
],
[
"0x1a5f7fc0f977a3020f8c5bd30c635fadab9b1ea0003e8255d90ec9ee48ff09ea",
9521042,
"BbrFR6tykrnKzskVJRcBJbWfDRcCeBqMweotE4bXVXLo"
],
[
"0x1d02df222b5149497a4d722514bf33a8bf755011724cdd3ced7ecb174d833690",
9521046,
"DcwkhBh2sdw8yJW1SH4tv5mVq6Ec7z4EmAaRL2H3AS27"
],
[
"0x1d8bf3742256d434ae9f1305eab214c5af458539c293ee51a25af8826b692589",
9521046,
"GG9mzv9JpXvAMeAC2FWPMXSqWqyTJiqeFepwLAzEpKgL"
],
[
"0x1e878332e7fa2e6fa6120a395bc8ad207ba5f5fa7cc8359ae4b56da20b50ff54",
9521056,
"GrK2met5oxjdVP3qJuXtNAs7hkajFFUwbe512LVxBBmW"
],
[
"0x1ed6d2105135371d901d24e4acd6fd9025b24d4d3d75277f2ad059825a5e5b38",
9521040,
"DJurdiKP82LVADxNrxsYnYJW9aCcDP1bxLbNbdcvdMHV"
],
[
"0x1f551af1739e258b5ed17c48e348c494de7598dc2a42aa2d5c0f284589de61e7",
9521037,
"4rpZtAY2eC2YyAJiDcDfq1BbCnV4sPKQQBU9rGCyTpmD"
],
[
"0x1fcf200bc9b970c9c877ddadddf28cdff19165c246914444b2968c7287587e8a",
9521056,
"5DDvPhXHRzmjdT9FRNqYe5T5LwkBpov2XKRpnNevhMJf"
]
],
"owner": "0x27147325dafdae103c7e8f09a82654ae7a4654c3042e1e278187013065be47b7",
"price": 1000,
"budget": 3000000
},
"expiration": "None"
}
}
}The following Rego expression validates that only a specific move call can be sponsored by the Gas Station.
The expression does the following:
-
Extract the Commands Array: Retrieve the array of commands from the payload.
-
Restrict to a Single Command: Ensure that only one command is allowed to prevent piggybacking, where the sender might attach an additional unauthorized move-call to another contract.
-
Verify Expected Fields: Confirm that the package, module, and function fields match the expected values.
-
Decodes the First Argument: Checks that the first argument of the move-call, when decoded as a string using
bcs.decode_typed, equals"hello".
package matchers
default move_call_matches = false
move_call_matches if {
cmds := input.transaction_data.V1.kind.ProgrammableTransaction.commands
count(cmds) == 1
mc := cmds[0].MoveCall
mc["package"] == "0xb674e2ed79db3c25fa4c00d5c7d62a9c18089e1fc4c2de5b5ee8b2836a85ae26"
mc.module == "allowed_module_name"
mc.function == "allowed_function_name"
argv_1 := input.transaction_data.V1.kind.ProgrammableTransaction.inputs[0].Pure
bcs.decode_typed(argv_1, "string") == "hello"
}Note: All field addresses in the Rego expression should begin with
input.
Note: For full Rego syntax details please see the Reference.
The helper function bcs.decode_typed enables decoding of bytes from BCS-serialized data. This function is particularly useful when you need to evaluate input parameters that are provided in byte format. Since BCS-encoded data cannot always be reliably reversed without knowing the precise type, you must specify the type to decode before invoking the function.
Usage Example:
output := bcs.decode(data_bytes, data_type)Supported Data Types:
| Data Type |
|---|
string |
u8 |
u16 |
u32 |
u64 |
address |
bool |
vector_string |
vector_u8 |
vector_u16 |
vector_u32 |
vector_u64 |
vector_address |
vector_bool |
The Rego expressions may come from different sources: file, redis and http.
access-controller:
access-policy: deny-all
rules:
- sender-address: "*"
rego-expression:
location-type: file
path: "./source_file.rego"
rego-rule-path: data.matchers.move_call_matches
action: allowaccess-controller:
access-policy: allow-all
rules:
- sender-address: "*"
rego-expression:
location-type: redis
url: "redis://localhost"
redis-key: source.rego
rego-rule-path: data.matchers.move_call_matches
action: allowaccess-controller:
access-policy: allow-all
rules:
- sender-address: "*"
rego-expression:
location-type: http
url: "http://localhost:8080/source.rego"
rego-rule-path: data.matchers.move_call_matches
action: denyThe Gas Usage Limit feature enables you to track gas consumption based on predefined parameters. When enabled, the gas tracking applies to the entire rule. The configuration syntax is:
gas-usage:
value: [range_of_numbers]
window: [duration]
count-by: [ sender-address ] # optionalNote: The syntax of
durationfollows the specification used in thehumantimecrate
Below are two examples that demonstrate how to enforce gas usage limits.
1. Limit Gas Usage per Address
This configuration sets a daily gas usage limit for a specific address. In the example below, the sender at address 0x0101... is restricted to a maximum daily usage of 10000000 gas units.
The time window is reset not on a daily reset time, but 24 hours (as configured below) after the first transaction of sender at address 0x0101..., allowing to have flexible usage based scheduling across different time zones.
Note that gas usage for other addresses remains unconstrained.
access-controller:
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
gas-usage:
window: 1 day
value: ">1000000"
action: deny
- sender-address: '*'
action: allow2. Limit Gas Usage per Address and Module
In this example, the configuration restricts the daily gas usage for a specific address when calling a designated module. The sender at address 0x0101... is limited to a maximum daily usage of 10000000 gas units when accessing package 0x0202.... For all other interactions, gas usage is blocked.
access-controller:
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
move-call-package-address: "0x0202020202020202020202020202020202020202020202020202020202020202"
gas-usage:
value: <1000000
window: 1 day
action: allow3. Limit Gas Usage per Address
In this example, each user is limited to 10000000 gas units per day. The additional property count-by allows you to maintain individual counters for each sender-address. Without count-by, all users would share a daily limit of 1000000 gas units.
access-controller:
access-policy: deny-all
rules:
- sender-address: "*"
gas-usage:
value: <1000000
window: 1 day
count-by: [ sender-address ]
action: allowAn external server (a hook), that decides whether a transaction should be executed or not can be configured. The hook receives the same input as the gas station allowing to parse inspect the transaction the same way, as the gas station does.
Hook server(s) can be configured as a term in the access controller rules, allowing to integrate hooks into existing rule sets or replacing the gas station built in access controller by a using hook only configuration.
Hooks are configured as values for the "action" keyword, by setting the action value to a URL instead of allow/deny.
Hooks are the last thing that is called in an access controller rule (just before the gas usage check due to safety reasons). This reduces the amount of calls against a hook server and leads to a few possible scenarios as shown below.
flowchart TD
Start(rule with hook<br>is processed)
CallHook(call<br>hook)
IgnoreHook(ignore<br>hook)
AllowTx(allow tx)
DenyTx(deny tx)
CheckNextRule(check<br>next<br>rule)
CheckPreviousTerm{previous<br>terms<br>apply}
CheckResponse{process<br>response}
Start --> CheckPreviousTerms
CheckPreviousTerms -->|yes| CallHook
CheckPreviousTerms -->|no| IgnoreHook
IgnoreHook --> CheckNextRule
CallHook --> CheckResponse
CheckResponse -->|allow| AllowTx
CheckResponse -->|deny| DenyTx
CheckResponse -->|noDecision| CheckNextRule
A hook server has to follow the api spec defined here. Also an example server that can be used as a starting point for an own hook can be found in our examples.
- Hook only configuration
Having a single rule with a hook action replaces the access controller completely:
access-controller:
access-policy: deny-all
rules:
- sender-address: "*"
action: http://127.0.0.1:8080or even shorter:
access-controller:
access-policy: deny-all
rules:
- action: http://127.0.0.1:8080As you usually might want to reduce the number of calls against the hook a bit, you can already apply access controller logic before the hook is called (all other terms except gas-usage are checked before the hook call). To do so, add terms as documented above, for example:
access-controller:
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
transaction-gas-budget: '<1000000' # allowed operators: =, !=, <, >, <=, >=
action: http://127.0.0.1:8080Hook actions don't have to be used as standalone rules and can integrate seamlessly with other rules, e.g.
access-controller:
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
transaction-gas-budget: '<1000000'
action: allow
- action: http://127.0.0.1:8080
- sender-address: "*"
gas-usage:
value: '<1000000'
window: 1 day
count-by: [ sender-address ]As this might look a bit confusing, let's break this one down:
- we have a privileged address
0x0101010101010101010101010101010101010101010101010101010101010101, that can send transaction below a certain threshold - other addresses or larger transactions by the privileged address have to go through a hook check
- the hook can then react with:
- allowing the transaction
- denying the transaction
- letting the next rule decide if the transaction should be executed or not
- assuming, the hook decides not to decide about the transaction, we would now check the sender address based gas usage and decide based on this if the transaction is executed or not
For more information about how the rules are processed, please refer to this link.