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

2 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

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.

2 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