Infernet
Node
Intro to Payments

Intro to Payments

In the previous section, we deployed a simple hello-world container with our Infernet node and went through an end-to-end process of creating both an off-chain compute job and an on-chain subscription request.

In the following sections, we will modify the hello-world project to include a payment flow. In summary, we will

  1. Modify the contract as well as the Infernet Node to include payment configurations.
  2. Deploy & fund an Infernet wallet, then approve the smart contract to spend our wallet's funds.
  3. Make a subscription request, and observe the payment flow.

Code for this Example

All the code for this example is under the projects/payment directory in the Infernet Container Starter (opens in a new tab) repository. In the tutorial below, we will walk through how to change the hello-world contract to include payment functionality, but feel free to reference the code in the repository for more details.

Clone the starter repository

The first step is the same as before, we will clone the repository. If you have already done this in the previous section, you can skip this step.

# Clone locally
git clone --recurse-submodules https://github.com/ritual-net/infernet-container-starter
# Navigate to the repository
cd infernet-container-starter

Modify the SaysGM contract

In the hello-world example, our SaysGM contract looked like this:

function sayGM() public {
    _requestCompute(
        "hello-world",
        bytes("Good morning!"),
        1, // redundancy
        address(0), // paymentToken
        0, // paymentAmount
        address(0), // wallet
        address(0) // prover
    );
}

We will modify this contract take in as input a payment amount, and a wallet to make the payment from:

 
function sayGM(uint256 paymentAmount, address wallet) public {
    _requestCompute(
        "hello-world",
        bytes("Good morning!"),
        1, // redundancy
        address(0), // paymentToken
        paymentAmount,
        wallet,
        address(0) // prover
    );
}

Modify how we call this SaysGM contract

We called the SaysGM contract in the previous example using a foundry script. That script is located under projects/hello-world/contracts/script/CallContract.s.sol:

contract CallContract is Script {
    function run() public {
        // Setup wallet
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);
 
        SaysGM saysGm = SaysGM(0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e);
 
        saysGm.sayGM();
 
        vm.stopBroadcast();
    }
}

We will modify this script to read the payment amount as well as the wallet address from the environment variables:

contract CallContract is Script {
    function run() public {
        // Setup wallet
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
 
        uint256 amount = vm.envUint("amount");
        address wallet = vm.envAddress("wallet");
 
        vm.startBroadcast(deployerPrivateKey);
 
        address registry = 0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e;
        SaysGM saysGm = SaysGM(registry);
 
        saysGm.sayGM(amount, wallet);
 
        vm.stopBroadcast();
    }
}

Modify the Infernet Node's Configuration

We now need to configure our Infernet Node to include a payment wallet. Our anvil-node (opens in a new tab) is already configured to include a wallet, whose owner is the same address that the private_key in the config.json file corresponds to.

We will see how such a wallet is created in an upcoming section, as we'll have to create the same wallet for the subscription consumer. But at this point we will simply modify the config.json file to include the pre-registered wallet address.

Under the projects/hello-world/container/config.json file, we will add the "payment_address" field to the wallet object:

    {
        // etc.
        "wallet": {
            "max_gas_limit": 4000000,
            "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
            "payment_address": "0x60985ee8192B322c3CAbA97A9A9f7298bdc4335C"
        },
        // etc.
    }

We also set a minimum required payment for a hello-world container request. The "accepted_payments" field is a mapping of token addresses to payment amounts. In this case, we will set the payment amount to 1 ETH, the token address being the zero address means that we're using ETH as the payment token. For any other token, you would use that token's address.

"containers": [
    {
        "id": "hello-world",
        "image": "ritualnetwork/hello-world-infernet:latest",
        // etc. etc.
        "command": "--bind=0.0.0.0:3000 --workers=2",
        // new field for payment
        "accepted_payments": {
            "0x0000000000000000000000000000000000000000": 1000000000000000000, // 1 ether
        }
    }
]

Deploy the Container & Contracts

At this point, we can go ahead and deploy the container and contracts as we did in the previous section.

Note: We will use the payment project for these commands, but if you've been following along by changing the hello-world project, you will also be able to use that project name.

make deploy-container project=payment

And in a separate terminal:

make deploy-contracts project=payment

This will give us the logs:

== Logs ==
Loaded deployer:  0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
Deployed SaysHello:  0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e
 
## Setting up 1 EVM.

We can see that our SaysHello contract has been deployed to address 0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e.

Create a Wallet

We now have to register a wallet, fund it & approve the smart contract to spend the wallet's funds. One could write scripts to do this, but for convenience we will make use of the infernet-client (opens in a new tab) cli tool.

This is a python package, so we will have to set up a new environment first, then install the package.

python3 -m venv env
source env/bin/activate
pip install infernet-client

Alternatively, you could do this via uv:

uv venv
source .venv/bin/activate
uv pip install infernet-client

Now, we can register a wallet:

infernet-client create-wallet --rpc-url http://localhost:8545 \
    --factory 0xF6168876932289D073567f347121A267095f3DD6 \
    --private-key 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a \
    --owner 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC

In the snippet above:

  • --rpc-url is the URL of the Anvil node.
  • --factory is the address of Infernet's wallet factory.
  • --private-key is the private key of the subscription consumer. This is Anvil's third test account.
  • --owner is the address of the initial owner of the wallet. In this case we want to create this wallet for ourselves, so we use the public address of the private key we provided.

Your should get an output like so:

Success: wallet created.
    Address: 0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6
    Owner: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC

Funding the Wallet

We will now fund the wallet with some ETH. We will use the infernet-client tool to do this as well:

infernet-client fund --rpc-url http://localhost:8545 \
--private-key 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a \
--wallet 0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6 \
--amount '10 ether'

We can see in the snippet above:

  • --rpc-url & --private-key are the same as before.
  • --wallet is the address of the wallet we just created.
  • --amount is the amount of ETH we want to fund the wallet with.

You should get an output like so:

Success: sent
    amount: 10 ether
    token: 0x0000000000000000000000000000000000000000
    to wallet: 0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6
    tx: 0xac45dd0d6b1c7ba77df5c0672b19a2cc314ed6b8790a68b5f986df3a34d9da12

At this point, we can check the balance of the wallet using foundry's cast cli:

cast b 0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6

You should see an output like so:

10000000000000000000

We can see that our wallet has now been funded with 1 ETH.

Approving the Contract to Spend the Funds

Using the same CLI tool, we can approve the contract to spend the wallet's funds:

infernet-client approve --rpc-url http://localhost:8545 \
    --private-key 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a \
    --wallet 0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6 \
    --spender 0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e \
    --amount '10 ether'

Here,

  • --rpc-url & --private-key are the same as before.
  • --wallet is the address of the wallet we just created.
  • --spender is the address of the contract we have just deployed. You can see this address in the logs from the previous step.

You should get an output like so:

Success: approved spender: 0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e for
amount: 10 ether
token: 0x0000000000000000000000000000000000000000
tx: 0x7c0b7b68abf9787ff971e7bd3510faccbf6f5f705186cf6e806b5dae8eeaaa30

Making a Subscription Request

At this point, we can continue to make a subscription request, and observe the payment flow.

You should already be able to see the logs from the infernet-node container from the command that we ran in the steps above, but if you closed that tab, you can run the following command to see the logs again:

docker logs -f infernet-node

Now we'll make our subscription request, this is the same as before, with the addition of the payment amount and wallet address:

make call-contract project=payment amount=1000000000000000000 wallet=0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6

In the infernet-node logs, you should see the following:

2024-06-06 02:01:34 [info     ] Collected highest subscription id                  [chain.listener] id=5 head_block=172
2024-06-06 02:01:34 [info     ] Checked for new subscriptions                      [chain.listener] last_synced=172 last_sub_id=5 head_sub_id=5
2024-06-06 02:01:34 [info     ] Container execution succeeded                      [chain.processor] id=5 interval=1
2024-06-06 02:01:34 [info     ] Sent tx                                            [chain.processor] id=5 interval=1 delegated=False tx_hash=0xf55df93846d6e9c9eab101bb2f381d8f96327587b6e5325e3d44cf64c17f2ab4

Checking the balance of the wallet again, you should see that the wallet has been debited by 1 ETH:

cast b 0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6

You should see an output like so:

9000000000000000000

Congratulations! 🎉 You have successfully created an on-chain subscription request with a payment! You can now continue to play around with the approve, minimum payment & payment parameters to see how the payment flow changes.

Additional Resources

  1. To look at the node configuration, code, and other resources for the payments container, reference the Infernet Container Starter (opens in a new tab) repository.
  2. To build your own Infernet-compatible container image, reference the Containers section.
  3. To learn about more complicated applications, have a look at Ritual Learn (opens in a new tab).