L1-L2 interaction via Postman
Postman is a Starknet utility that allows testing L1-L2 interaction. It is unrelated to the Postman API platform. Ensure you have an L1 node and a Devnet (L2 node) running, load a messaging contract, and flush the queue to transmit the messages to their destinations. The functionality relies on two internal message queues: one for L1->L2 messages, another for L2->L1 messages.
You can use starknet-devnet-js
to perform these actions, as witnessed in this example, or directly send requests to the endpoints specified below.
Load
POST /postman/load_l1_messaging_contract
{
"network_url": "http://localhost:8545",
"address": "0x123...def"
}
JSON-RPC
{
"jsonrpc": "2.0",
"id": "1",
"method": "devnet_postmanLoad",
"params": {
"network_url": "http://localhost:8545",
"address": "0x123...def"
}
}
Loads a MockStarknetMessaging
contract. The address
parameter is optional; if provided, the MockStarknetMessaging
contract will be fetched from that address, otherwise a new one will be deployed.
network_url
is the URL of the JSON-RPC API of the L1 node you've run locally or that already exists; possibilities include, and are not limited to:
Loading a messaging contract is a dumpable event, meaning that, if you've enabled dumping, a messaging-contract-loading event will be dumped. Keep in mind that, if you rely on Devnet deploying a new contract, i.e. if you don't specify a contract address of an already deployed messaging contract, a new contract will be deployed at a new address on each loading of the dump. Read more about dumping here.
Flush
POST /postman/flush
JSON-RPC
{
"jsonrpc": "2.0",
"id": "1",
"method": "devnet_postmanFlush"
}
Goes through the newly enqueued messages since the last flush, consuming and sending them from L1 to L2 and from L2 to L1. Use it for end-to-end testing. Requires no body. Optionally, set the dry_run
boolean flag to just see the result of flushing, without actually triggering it:
POST /postman/flush
{ "dry_run": true }
JSON-RPC
{
"jsonrpc": "2.0",
"id": "1",
"method": "devnet_postmanFlush",
"params": {
"dry_run": true
}
}
A running L1 node is required if dry_run
is not set.
Flushing is not dumpable, meaning that, if you've enabled dumping, a flushing event will not itself be re-executed on loading. This is because it produces L2 messaging events that are themselves dumped. No L1-side actions are dumped, you need to take care of those yourself. Read more about dumping here.
Disclaimer
This method of L1-L2 communication testing differs from how Starknet mainnet and testnets work. Taking L1L2Example.sol (originally from Starknet documentation, no longer available there):
constructor(IStarknetCore starknetCore_) public {
starknetCore = starknetCore_;
}
The constructor takes an IStarknetCore
contract as argument, however for Devnet's L1-L2 communication testing, this has to be replaced with the logic in MockStarknetMessaging.sol:
constructor(MockStarknetMessaging mockStarknetMessaging_) public {
starknetCore = mockStarknetMessaging_;
}
Mock transactions
L1->L2
A running L1 node is not required for this operation.
The L2 target entrypoint must be an l1_handler
.
Sends a mock transactions to L2, as if coming from L1, without the need for running L1. The target L2 contract's address must be provided to l2_contract_address
and the entry_point_selector
must refer to a public method of the target contract. The method must be annotated with l1_handler
, otherwise an ENTRYPOINT_NOT_FOUND
error may be returned.
In regular (non-mocking) L1-L2 interaction, nonce
is determined by the L1 Starknet contract. In this mock case, it is up to the developer to set it.
POST /postman/send_message_to_l2
Request:
{
"l2_contract_address": "0x00285ddb7e5c777b310d806b9b2a0f7c7ba0a41f12b420219209d97a3b7f25b2",
"entry_point_selector": "0xC73F681176FC7B3F9693986FD7B14581E8D540519E27400E88B8713932BE01",
"l1_contract_address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"payload": [ "0x1", "0x2" ],
"paid_fee_on_l1": "0x123456abcdef",
"nonce": "0x0"
}
JSON-RPC
{
"jsonrpc": "2.0",
"id": "1",
"method": "devnet_postmanSendMessageToL2",
"params": {
"l2_contract_address": "0x00285ddb7e5c777b310d806b9b2a0f7c7ba0a41f12b420219209d97a3b7f25b2",
"entry_point_selector": "0xC73F681176FC7B3F9693986FD7B14581E8D540519E27400E88B8713932BE01",
"l1_contract_address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"payload": [ "0x1", "0x2" ],
"paid_fee_on_l1": "0x123456abcdef",
"nonce": "0x0"
}
}
Response:
{ "transaction_hash": "0x0548c761a9fd5512782998b2da6f44c42bf78fb88c3794eea330a91c9abb10bb" }
L2->L1
Sends a mock transaction from L2 to L1. The deployed L2 contract address from_address
and to_address
must be valid.
It is a mock message, but only in the sense that you are mocking an L2 contract's action, which would normally be triggered by invoking the contract via a transaction. So keep in mind the following:
A running L1 node is required for this operation.
POST /postman/consume_message_from_l2
Request:
{
"from_address": "0x00285ddb7e5c777b310d806b9b2a0f7c7ba0a41f12b420219209d97a3b7f25b2",
"to_address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"payload": ["0x0", "0x1", "0x3e8"],
}
JSON-RPC
{
"jsonrpc": "2.0",
"id": "1",
"method": "devnet_postmanConsumeMessageFromL2",
"params": {
"from_address": "0x00285ddb7e5c777b310d806b9b2a0f7c7ba0a41f12b420219209d97a3b7f25b2",
"to_address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
"payload": ["0x0", "0x1", "0x3e8"],
}
}
Response:
{"message_hash": "0xae14f241131b524ac8d043d9cb4934253ac5c5589afef19f0d761816a9c7e26d"}