Skip to main content
Version: 0.2.4

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:

Dumping and Loading

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.

Dumping and Loading

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

note

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:

note

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"}