How to accept ICP payments?

ICP (Canister: NNS ICP Ledger - IC Dashboard) does not implement approve() method to do payments on behalf to users.

So, I have two choices:

  • Use query_blocks() with user-supplied payment ID. This way if user’s UI crashes in the middle of a payment, we are not notified about the payment and have an angry user who lost money.
  • Use Wrapped ICP to accept ICP payments. It is not convenient for users.

So, what is the correct method to accept ICP payments?

What you are looking for is ICRC-2, but that hasn’t been standardised quite yet (ETA very soon).

My personal favourite option is to transfer the tokens into a user-specific subaccount. This account can then always be queried for its balance to see if any previous attempts failed in-between transferring ICP and triggering the payment.

The canister can then be notified of the current balance, attempt to transfer the tokens to its main account, and if the transfer succeeds credits the payment to the user

3 Likes

If the user opens two tabs and submits two orders from two tabs, and somehow both tabs crash in the middle, then having a subaccount unique for the user is not enough, need also before the transfer record his/her two orders (and even if just one order, anyway need to write the item that the user ordered).

But recording orders can be exploited by a hacker who would record like 10M of orders to drain cycles from my canister.

However, is it an issue? The hackers have other ways to drain cycles, e.g. by (even worse) posting spam messages to my social network. Moreover, my AWS sites were not spammed this way, why should I worry more when the site is on IC?

Having ICRC-2 would, however, help: The number of successful approve() calls is limited by the hacker’s balance.

So, what to do?

You can also make a subaccount for every user:order pair. And yes, draining cycles is possible in a few ways. But you have a similar problem on AWS where attackers can also consume a lot of computation for you

1 Like

We have developed a library here called TokenHandler: motoko-lib/TokenHandler.mo at main · research-ag/motoko-lib · GitHub

It manages deposits made by users to a canister (which usually represents some service). It can be used for any ICRC-1 token including ICP. If users accidentally double pay the credit will be tracked. Users can withdraw excess credit. It is safe even if some calls to the ledger fail. Funds can’t get lost.

As you noticed the absence of approvals makes things a little harder. TokenHandler internally uses locks and with approvals in the ledger it could have been made lock-free.

3 Likes

Sorry, it’s not well documented and hasn’t been used anywhere besides some internal demos. We did it mainly to get experience with icrc1. It should be noted that unlike other strategies for handling deposits this one is purely balance-based. This means it only ever looks at the current balance in the subaccounts, not at individual deposit transactions. Users do not have to track the transaction ids or block heights for the deposits they made.

You can also use deploy an Invoice Canister, which includes all the logic to create an invoice for an amount, verify the amount has been paid, handle refunds, and so forth. The verify_invoice method can be called at any point, even multiple times, and is designed with asynchronous user payment flows in mind

1 Like

Hey @kpeacock is the invoice canister a prove of context or can I use it in my project to sell something and get paid in ICP?

Until recently it was considered production ready, when the financial integration team decided to stop supporting it. I’ll try to keep it maintained in my spare time, but we’ve moved it to my personal github now. The code hasn’t changed since then, and the code is fully audited, so it still should work for your project

Thanks awesome, I will give it a try and can I contact you if I have a question?

Go for it! Also, feel free to start a new thread if you encounter specific issues

1 Like

Hey @kpeacock I have a short question to the creation of an invoice.

There is the variable let ONE_ICP_IN_E8S = 100_000_000; This value represents a fraction of 1 ICP.

In the seller canister there is a function called create_invoice() and it does the following calculation:

amount = ONE_ICP_IN_E8S * 10 * invoiceArgs.amount;

My question is, what does the multiplication by 10 here mean ?

It’s been a while - hard to remember the exact reason I did that. It probably should be simplified to just ICP_IN_E8S times amount

ok, if 1 ICP is aka 100_000_000, then there is no need to do this multiplication. To store e.g. 1.2 ICP I would store 120_000_000 or 0.8 ICP with 80_000_000 as a Nat type. Correct ?