Differences between enterprise addresses and base addresses. "address_pool_gap" parameter

I’m doing a project in which the users of my platform pay a certain amount of ADA to use a service. For this, my server machine provides them with an address to send the payment in order to use a specific service. I do not care to know which user sent the payment. But I do care to know that that payment was completed. To do this, in the wallet of my server machine, I review the transactions received and check the addresses of those transactions to verify that the payment was made. Since my platform will have multiple services that you can pay for, I will need multiple addresses that my wallet generates.

To do this, I am using the Cardano wallet API:
github: input-output-hk/cardano-wallet

For the generation of keys and addresses I use the Cardano official documentation library “Cardano Serialization Lib”:

Here comes the complicated part and you have to have knowledge of address generation in Cardano.

The cardano-wallet API provides a GET query to get a list of 20 unused addresses. To these addresses are added the addresses already used but the library has the obligation to always show 20 unused. This parameter is configured when creating a wallet with the name “address_pool_gap”, described in the following documentation:

An example of this GET query would be:

localhost:8090/v2/wallets/{addressId}/addresses

[{"state":"unused","id":"addr_test1qq6c8gvykc5aeq3hetxnrw6kzn3zpq6lw8m4t7p7yyc93swqd42kad2psa767zynpd2ut4mx54xh4ccu7ztc7d2wttcqg67nrk","derivation_path":["1852H","1815H","0H","0","0"]},
{"state":"unused","id":"addr_test1qq5qxxsr3ffjmx7mkjt6m40kepg3uprz0ueu9hnlv6gxnxwqd42kad2psa767zynpd2ut4mx54xh4ccu7ztc7d2wttcqm67z5t","derivation_path":["1852H","1815H","0H","0","1"]},
... (2, 3, ..., 18,)
{"state":"unused","id":"addr_test1qrfhrwljs07sy6jrgf9087pryeaq7rrmm0zvrh5pv0uk3ywqd42kad2psa767zynpd2ut4mx54xh4ccu7ztc7d2wttcqmge5rr","derivation_path":["1852H","1815H","0H","0","19"]},
{"state":"unused","id":"addr_test1qzgf2lhcq98zu82vu2vgrptxzu7q8asudwwncg5988a2thxqd42kad2psa767zynpd2ut4mx54xh4ccu7ztc7d2wttcqe0qufc","derivation_path":["1852H","1815H","0H","0","20"]}]

Since I’m going to need multiple addresses from where I’m going to receive payments, I cannot be satisfied with the 20 default addresses nor with 10000 setting the parameter “address_pool_gap” to the maximum (Although changing this parameter from “address_pool_gap” could slow down operations, as it says in the documentation. Which I don’t understand why).

So I decided to use Cardano Serialization Lib. I decided to create one by one addresses as they are needed. Using the following code described in the Cardano developer documentation. Serialization-Lib.

// base address with staking key
const baseAddr = CardanoWasm.BaseAddress.new( CardanoWasm.NetworkInfo.testenet().network_id(), CardanoWasm.StakeCredential.from_keyhash(utxoPubKey.to_raw_key().hash()), CardanoWasm.StakeCredential.from_keyhash(stakeKey.to_raw_key().hash()));

Since all addresses start from the same root key for their generation. Following the BIP-44 standard → “1852H”, “1815H”, “0H”, “0”, “0” You get the same addresses as those provided by the cardano-wallet API. I mean, those of the GET query discussed above. You only have to increase the final derivation value that generates a new address, in this way:
“1852H”, “1815H”, “0H”, “0”, “1”,
“1852H”, “1815H”, “0H”, “0”, “2”,
“1852H”, “1815H”, “0H”, “0”, “n”,
etc.

The problem comes when I’m going to generate the address number 21. By having the parameter in “address_pool_gap” set to 20 in the wallet, the address 21 does not receive payments sent from a Daedalus wallet. It only works if you increment the value of “address_pool_gap” or if you use one of the unused addresses at least. For example, for address 22 you would need to previously use 2 of the 20 unused and so on. Due to this, the following two addresses would automatically be created, that is, 21 and 22. In this way they would already be available to use (payments are received). I don’t understand why this is so.

So I finally decided to create enterprise addresses as follows:

// enterprise address without staking ability, for use by exchanges/etc
const enterpriseAddr = CardanoWasm.EnterpriseAddress.new( CardanoWasm.NetworkInfo.mainnet().network_id(), CardanoWasm.StakeCredential.from_keyhash(utxoPubKey.to_raw_key().hash()));

These addresses are shorter (they have fewer characters) but I can create as many as I want and at any time they are available to receive payments. Here are my doubts:

  • What is the difference between enterprise addresses and base addresses?

  • Why does a payment to a “base address” with a derivation index higher than “address_pool_gap” not receive the ADAs when I show the wallet balance?

  • How can I make address 21 receive payments without having to use an address from 0 to 20 and without modifying the “address_pool_gap” parameter?

  • Why does modifying the “address_pool_gap” parameter cause performance degradation as stated in the documentation?

  • What security problems can the distribution of “enterprise address” have? I mean, could a malicious person discover the recovery phrases and access the wallet?

  • This approach of generating several addresses from the same public key so that they can make payments and in this way find out that the transaction was completed, is it the most correct?

All my respects to those who know how to resolve these doubts as they don’t seem simple. Consider yourself an expert in using Cardano if you know how to solve them :smiley:

1 Like

The enterprise keys are shorter because they don’t include a stake key hash.

Setting address_pool_gap to 20 is a convention that is generally adopted, not a requirement, but setting it to higher than 20 may cause tools such as wallets and explorers to not see the addresses used after a gap of 20 unused addresses. Funds can still be send to and spent from those addresses (since they are perfectly valid on the blockchain), but those addresses and the funds might not be displayed by some tools. The reason for this is that an unlimited number of addresses can be generated by the key-derivation algorithm, but it is not practical for a wallet to check an unlimited number: the convention is to only check the sequence of addresses until 20 unused addresses are found, and then assume that all subsequent addresses aren’t used either.

2 Likes

See @bwbush answer above.

It does however wallet applications including API will not scan beyond the gap by default. Lookup the transaction on cardano explorer to confirm.

It is best not to even modify the gap. In fact there is nothing wrong with reusing the same address repeatedly. Is there any advantage to NOT using the first 0-20 receive addresses?

The larger the gap that needs to be scanned the longer it will take to load wallet history. Like a sentinel value in an endless loop. The sooner you can find it the sooner you can stop searching!

No. The wallet addresses are derived from the public key. By default each hash is actually incrementally generated from the base. There is no requirement to use sequential ordering however there is also no reason to skip addresses either.

Have you considered differentiating the service by name or id instead of address destination? You could then have a single receive address for all types of payments with a small amount of metadata per transaction for example.

2 Likes

First of all, thank you for your answer, it’s a great help to me.

Do you know if there is any known tool where you can check the balance of those “base addresses”? I think in the input-output-hk/cardano-wallet API you can’t.

Is there any problem or inconvenience in using “enterprises addresses” for my use case? I mean, expose those “enterprise addresses” on the client side as needed and then check in the wallet transactions if those addresses contain the corresponding lovelace?

There are many blockchain explorer tools. Perhaps visit cardanoscan.io and have a play?

1 Like

Thank you very much for your answer DinoDude :slight_smile:

The application is developed in nodejs. Some API, library or I had thought about using cardano-graphql or cardano-db-sync is useful to me. But the latter requires quite a few hours of time to learn about the cardano-graphql queries or the cardano-db-sync postgresql database.

The idea of the application is to dispatch addresses as payments are needed. From my server wallet I check the transactions to see if the corresponding payment was completed from that address that I offered.

It’s a good idea about the metadata. But I would not control the delivery of that transaction. The client would do it from his Daedalus, Yoroi wallet or from wherever he wants. So I can not add metadata that identifies the transaction and thus know if that client with that address that was provided completed the payment.

Is there any inconvenience in creating new “enterprise addresses” as needed and exposing them to the clients to make the payment? There is not much talk about “enterprise addresses” it seems that people don’t like it :rofl:

Not too much inconvenience, your application just needs to do the bookkeeping to keep track of the addresses, the corresponding keys, and which addresses were provided to which customers.

Jumping on this track after so much time I have a question for you guys.
Imaginig that I’m creating adresses like @Nachograce described, and I have around 100 unused addresses but the 101th received some ADA.
Since the wallets will not show up this transaction how can I move the funds that are in this address?
Is there a way, for example, to create a transaction with the CLI specifying the source address and the destiny?

Yes. You can specify whatever inputs and outputs you want.

If you do not know what those are then worst case scenario you can iterate all the UTXOs, derive every possible address, etc, etc. That’s neither reasonable nor recommended. Especially since you can just take the first unused address or do something sane that will not challenge the implementation of every wallet or use case in existence.

As stated years ago Occam’s Razor still applies so strive for simplicity. There is still no logical reason to arbitrarily create gaps, use more than one address for merely collecting payments, or providing any inputs/outputs in transactions that your application makes which your application cannot use.

For whatever reason if you insist on this strategy then you need some accounting model or book keeping as mentioned once upon a time. This could be a simple map or something more complex to ensure you are tracking everything needed so you can find it later more easily.