Infernet
Node
Payments

Intro to Payments

ℹ️

You can skip this section if you're not interested in receiving payments.

In the previous sections, 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 monitor the payment flow.

Clone the starter repository

All the code for this example is under the projects/payment directory in the Infernet Container Starter (opens in a new tab) repository. Please reference the source code for more details.

ℹ️

You can skip this step if you cloned the repository in one of the previous sections.

# 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 Onchain Subscription 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 call to SaysGM

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);
 
        SaysGM saysGm = SaysGM(0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e);
 
        saysGm.sayGM(amount, wallet);
 
        vm.stopBroadcast();
    }
}

Modify the Infernet Node

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:

  {
    // ...
    "wallet": {
        "max_gas_limit": 4000000,
        "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
        // Add: payment address
        "payment_address": "0x60985ee8192B322c3CAbA97A9A9f7298bdc4335C"
    },
    // ...
  }

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, we would instead use that token's address.

{
  // ...
  "containers": [
    {
      "id": "hello-world",
      // ...
      // ADD: accepted payment tokens
      "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.

ℹ️

We will use the payment project below, but if you've been following along and modified the hello-world project, you can also use the hello-world project name instead.

Deploy the node with the payment container:

make deploy-container project=payment

Deploy the modified SaysGM consumer contract:

make deploy-contracts project=payment

This will give us the logs:

== Logs ==
Loaded deployer:  0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
Deployed SaysGM:  0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e

# Setting up 1 EVM.

We can see that our SaysGM 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) library.

infernet-client 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

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.

The output should look like this:

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

Fund the Wallet

We will now fund the wallet with some ETH.

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

We can see in the snippet above:

  • --wallet is the address of the wallet we just created.
  • --amount is the amount of ETH we want to fund the wallet with.

The output should look like this:

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

The output should look like this:

10000000000000000000

Our wallet has now been funded with 1 ETH!

Approve contract to spend Wallet's funds

We can now 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'

We can see in the snippet above:

  • --spender is the address of the contract we have just deployed. You can see this address in the logs from the previous step.

The output should look like this:

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

Making a Subscription Request

We are now ready 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

We'll now make a subscription request with the addition of a 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 balance has decreased by 1 ETH:

cast b 0x7749f632935738EA2Dd32EBEcbb8B9145E1efeF6

The output should look like this:

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)