Deposits
Deposits are initiated on the L1 by the user. They send a transaction to the so-called DispatchContract, which is
unique to each appchain.
This contract has the responsibility to make sure that for every deposit message that your appchain receives, an actual transfer from the user to the settlement contract happened. That logic is validated by the Mina blockchain via this contract/zkapp. After that validation, the dispatch contract dispatches a message to the appchain that provably has to be honored.
Implementing deposits
To enable deposits (or any type of L1 -> L2 call), add the @runtimeMessage() decorator to your runtime method
(instead of using @runtimeMethod()).
import { runtimeModule, runtimeMessage } from "@proto-kit/module";
import { Deposit } from "@proto-kit/protocol";
import { Balances, UInt64 } from "@proto-kit/library";
@runtimeModule()
export class DepositBalances extends Balances {
// ...
@runtimeMessage()
public async deposit(deposit: Deposit) {
await this.mint(
deposit.tokenId,
deposit.address,
new UInt64(deposit.amount)
);
}
}Additionally, we need to tell the DispatchContract which runtime method it is allowed to call.
We do this by adding the path to the method to the contract’s config using a moduleName.methodName syntax.
Be careful to use the module name, not the class name - they can be different!
appchain.configure({
// ...
Protocol: {
...Protocol.defaultConfig(),
SettlementContractModule: {
SettlementContract: {},
BridgeContract: {},
DispatchContract: {
incomingMessagesMethods: {
deposit: "Balances.deposit",
},
},
},
},
})Depositing tokens
Now that our appchain is ready to accept deposits (after deploying the settlement contract with the right runtime + config of course), we can look at how to use the mechanism.
A deposit is always a two-step flow:
- A user submits an L1 transaction to the
DispatchContractthat locks the tokens in the settlement contract and emits a dispatch action containing the deposit message. - After L1 finality (see Finality considerations), the sequencer picks
up the dispatched action and forwards it to your
@runtimeMessage()— in our exampleBalances.deposit— which credits the user on the L2.
You can trigger step 1 either from the protokit CLI or programmatically by calling the BridgingModule directly.
The protokit CLI exposes a bridge deposit command that wraps the dispatch contract call for you.
It uses the same environment variables your appchain already relies on (dispatcher address, Mina network, etc.),
so in most cases you only have to provide the amount, token and the recipient on the L2.
# Deposit native Mina from the signer's L1 account to the same address on the L2
pnpm protokit bridge deposit <tokenId> <fromPrivateKey> <toPublicKey> <amount>tokenId is 0 for MINA and the custom token’s tokenId for everything else
Once the L1 transaction is included and finalized, the sequencer will automatically dispatch the message
and your Balances.deposit runtime message will be executed — no further action is required from the user.