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
- Modify the contract as well as the Infernet Node to include payment configurations.
- Deploy & fund an Infernet wallet, then approve the smart contract to spend our wallet's funds.
- 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
- 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. - To build your own Infernet-compatible container image, reference the
Containers
section. - To learn about more complicated applications, have a look at Ritual Learn (opens in a new tab).