Functions
Functions are implemented in the actions directory (or whatever is configured, see configuration page for more details. If you initialized actions project using tenderly actions init, you have example.ts (or .js) file which implements a function.
A function must match ActionFn type:
1
import { ActionFn, BlockEvent, Context, Event } from '@tenderly/actions'
2
โ€‹
3
export const blockHelloWorldFn: ActionFn = async (context: Context, event: Event) => {
4
let blockEvent = event as BlockEvent
5
console.log(blockEvent)
6
}
Copied!
Or for other types of trigger:
1
let transactionEvent = event as TransactionEvent
2
let periodicEvent = event as PeriodicEvent
3
let webhookEvent = event as WebhookEvent
Copied!
Event type must match trigger type for a given action from the configuration.
Or if you are using JavaScript:
1
const blockHelloWorldFn = async (context, event) => {
2
console.log(event)
3
}
4
module.exports = { blockHelloWorldFn }
Copied!
If you deploy and manually trigger this action, you will see console logs when you navigate to specific execution in the dashboard.

Secrets

Secrets feature lets you securely store credentials such as API tokens that your function needs.
Each secret is a key-value pair which is stored encrypted. To create secrets, go to dashboard and navigate to Actions -> Secrets. Your secrets are available in your function through context.
1
export const blockHelloWorldFn: ActionFn = async (context: Context, event: Event) => {
2
const token = await context.secrets.get('API_TOKEN')
3
}
Copied!
Be careful when working with secrets as logged secrets will be visible in execution logs to anyone with access to your project.
Secrets are created for each project and are available in every action in the project.

Storage

Your actions might want to persist data across runs. You can use the project's storage if you don't want to bother with external storage like your database.
Storage is a key-value store, accessible through context. You can store up to 1000 keys, with up to 1KB in key size and 10KB in value size.
1
export const blockHelloWorldFn: ActionFn = async (context: Context, event: Event) => {
2
await context.storage.putNumber('HELLO_WORLD/BLOCK_NUMBER', event.blockNumber)
3
await context.storage.putStr('HELLO_WORLD/BLOCK_HASH', event.blockHash)
4
โ€‹
5
let invocationCount = await context.storage.getNumber('HELLO_WORLD/INVOCATIONS')
6
await context.storage.putNumber('HELLO_WORLD/INVOCATIONS', invocationCount + 1)
7
}
Copied!
You can find available getters/setters in the Storage interface here.
If you read a key that does not exist from storage, you will get default value based on a getter you used:
  • getNumber returns number 0
  • getBigInt returns BigInt(0)
  • getStr returns an empty string
  • getJson returns an empty object
Storage is shared for all actions in the project. It is recommended that you isolate using namespaces, e.g.HELLO_WORLD/BLOCK_NUMBER instead of just BLOCK_NUMBER.
When using a manual trigger, you must choose the storage type. You can choose between creating a new empty storage (EMPTY), use project's storage (WRITE) or create new storage from the project's storage (COPY_ON_WRITE). For example, if the project's storage has INVOCATION = 1 and your action reads and increments this key:
  • if you use EMPTY the result will be 1 - incremented from default 0
  • if you use WRITE the result will be 2 and it will be visible in the project's storage
  • if you use COPY_ON_WRITE the result will be 2 but it won't be visible in the project's storage
Be careful when using WRITE storage type. Changes will be persisted in the project's storage and there is currently no way to roll back changes made by specific execution of an action.

Development

If your action logic is more complex (and it's maybe not working as expected) you might want to use a debugger. You can avoid deploying your action to test it if you setup local development. To make this a bit easier, we are providing you with a testing library. Here is an example how to use this library to execute action in unit tests:
1
import { TestRuntime, TestBlockEvent } from "@tenderly/actions-test";
2
โ€‹
3
test('block hello world', async () => {
4
// If you use TestRuntime, any changes to storage won't reflect
5
// in production storage.
6
let runtime = new TestRuntime()
7
8
// Test* events are available for other types.
9
let event = new TestBlockEvent()
10
event.blockHash = '0x123456789'
11
โ€‹
12
// If you use TestRuntime, you can pre-populate secrets.
13
// In real runtime, you can add secrets through dashboard only.
14
runtime.context.secrets.put("API_TOKEN", "test-token")
15
โ€‹
16
// Run function.
17
await runtime.execute(blockHelloWorldFn, event)
18
โ€‹
19
// Verify storage was modified as expected.
20
// This checks in-memory test storage and not a real production storage!
21
let result = await runtime.context.storage.getStr(
22
'BLOCK_HELLO_WORLD/LAST_BLOCK_HASH'
23
)
24
expect(result).toBe('0x123456789')
25
});
Copied!
โ€‹
Next, we are going to describe how to configure an action.
Last modified 7d ago
Copy link