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
- 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 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
- 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)