Balance
ETH is stored in the World
contract and owned by namespaces.
Any ETH sent with a call to a System
is kept on the World
, and credited to the namescape to which the System
belongs.
This has two important effects:
- The ETH balance is shared between the different
System
contracts in the same namespace of the sameWorld
. - The same
System
can be used in multipleWorld
contracts and the ETH balances remain separate.
To know how much ETH a call has included a System
can check _msgValue()
(opens in a new tab).
To know how much ETH a specific namespace has you can look in world:Balances
(the Balances
table of the world
namespace).
import { Balances } from "@latticexyz/world/src/codegen/tables/Balances.sol";
uint256 balance = Balances.get(<namespace>);
See this in action
-
Have a MUD application running. The easiest way to do this is to run the template locally.
-
Here we are not concerned with the client, so change to the
contracts
page. Because we are not concerned with the client, all the file names will be relative to.../packages/contracts
.cd packages/contracts
-
Create a
.env
file with:PRIVATE_KEY
- the private key of an account that has ETH on the blockchain.WORLD_ADDRESS
- the address of theWorld
to which you add the namespace.
If you are using the template with a fresh
pnpm dev
, then you can use this.env
:.env# Anvil default private key for the second account # (NOT the account that deployed the World) PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d # Address for the world we are extending WORLD_ADDRESS=0x6e9474e9c83676b9a71133ff96db43e7aa0a4342
-
Create this file as
scripts/GetBalance.s.sol
.GetBalance.s.sol// SPDX-License-Identifier: MIT pragma solidity >=0.8.21; import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; import { Balances } from "@latticexyz/world/src/codegen/tables/Balances.sol"; import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol"; import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; contract GetBalance is Script { function run() external { address worldAddress = vm.envAddress("WORLD_ADDRESS"); StoreSwitch.setStoreAddress(worldAddress); console.log("World at:", worldAddress); ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("")); console.log("Namespace ID: %x", uint256(ResourceId.unwrap(namespaceResource))); uint256 balance = Balances.get(namespaceResource); console.log("Balance: %d wei", balance); } }
Explanation
// SPDX-License-Identifier: MIT pragma solidity >=0.8.21;
Standard Solidity boilerplate
import { Script } from "forge-std/Script.sol"; import { console } from "forge-std/console.sol";
Standard
forge
script (opens in a new tab) boilerplate.import { Balances } from "@latticexyz/world/src/codegen/tables/Balances.sol";
The
Balances
table contains namespace balances. Note that while this table's Solidity code is available as part of the library, it is a standard MUD table and as such gets created from amud.config.ts
file (opens in a new tab).import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
We need this file (opens in a new tab) to specify the
World
's address.import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";
We need the namespace's
ResourceId
.contract GetBalance is Script { function run() external { address worldAddress = vm.envAddress("WORLD_ADDRESS");
Get
WORLD_ADDRESS
from.env
.StoreSwitch.setStoreAddress(worldAddress); console.log("World at:", worldAddress);
Set the address of the
World
(opens in a new tab) so future data calls to go to the correct location.ResourceId namespaceResource = WorldResourceIdLib.encodeNamespace(bytes14("")); console.log("Namespace ID: %x", uint256(ResourceId.unwrap(namespaceResource)));
Create the
ResourceId
for the root namespace.uint256 balance = Balances.get(namespaceResource); console.log("Balance: %d wei", balance); } }
Finally, get the balance from the
world:Balances
table. -
Source the
.env
file (we are going to use the variables there to transfer ETH to the root namespace's account).source .env
-
Run the script.
forge script script/GetBalance.s.sol --rpc-url http://localhost:8545
The balance should be zero for now.
-
To allow
increment
to accept wei, editsrc/systems/IncrementSystem.sol
to make itpayable
:IncrementSystem.sol// SPDX-License-Identifier: MIT pragma solidity >=0.8.21; import { System } from "@latticexyz/world/src/System.sol"; import { Counter } from "../codegen/index.sol"; contract IncrementSystem is System { function increment() public payable returns (uint32) { uint32 counter = Counter.get(); uint32 newValue = counter + 1; Counter.set(newValue); return newValue; } }
-
pnpm dev
deploys the new code, but it deploys it to a newWorld
. Restartpnpm dev
to have aWorld
in the same address as before. -
Call
increment
with ETH.cast send --private-key $PRIVATE_KEY 0x6E9474e9c83676B9A71133FF96Db43E7AA0a4342 --value 1ether "increment()"
-
Run the script again to see that the balance is higher.
forge script script/GetBalance.s.sol --rpc-url http://localhost:8545
To transfer ETH out of the World
you need to have the access
permission level to the namespace itself.
Note that System
contracts within the namespace do have that permission.
You use either transferBalanceToNamespace
(opens in a new tab), if you want to transfer between namespaces in the same World
, or transferBalanceToAddress
(opens in a new tab), to transfer to a different address.
See this in action
These steps assume you already transferred 1 ether to the root namespace.
-
Run this command to transfer 10 wei to the zero address.
cast send --private-key $PRIVATE_KEY 0x6E9474e9c83676B9A71133FF96Db43E7AA0a4342 "transferBalanceToAddress(bytes32,address,uint256)" 0x6e73000000000000000000000000000000000000000000000000000000000000 `cast address-zero` 10
-
See that the namespace's balance is lower by 10 wei.
forge script script/GetBalance.s.sol --rpc-url http://localhost:8545
-
See that 10 wei have been deducted from the balance of the
World
.cast balance $WORLD_ADDRESS
If you need to transfer ETH in a System
that has access to a namespace (for example, because it is in that namespace), you can use a function similar to this:
function _transfer(address to, uint amount) private {
IWorld(_world()).transferBalanceToAddress(WorldResourceIdLib.encodeNamespace("<namespace goes here>"), to, amount);
}