Asset Table for Wallets

I work on a wallet in my spare time. I save some notes here. May be useful to others too.

This is an SQL query to get relevant asset info (not checked, can have bugs)

SELECT asset_name, asset_longname, (SELECT sum(locked) FROM issuances WHERE issuances.asset = assets.asset_name AND status = "valid") AS locked, (SELECT divisible FROM issuances WHERE issuances.asset = assets.asset_name AND status = "valid" ORDER BY tx_index ASC LIMIT 1) AS divisible FROM assets

image

The entire table of all XCP assets is < 5MB.

A wallet that has this table will rely less on external APIs for divisibility and asset ID.

Without such a local table, different asset types depend on APIs to various degrees.

ASSET NAME vs ASSET ID

  • BTC & BTC have IDs 0 and 1. Divisible. Easy enough to hard-code in functions.

  • Regular named assets (like JPJA) have a mathematical two-way function to get from name to ID and back.

  • Numerical assets (like A14343250413609995459) have IDs which are simply their numbers.

  • Sub-assets (TYSONFURY.KO) are a special case of numerical assets. If entering the name in a wallet, the wallet should use this table (not an API) to lookup the ID. The subasset-ID relationship is set at the initial issuance and never changes.

ASSET DIVISIBILITY

This applies to regular, numeric and sub assets alike. Divisibility can change through a reset until the asset is locked.

Assets with locked value >= 1 have immutable divisibility.

All other assets should request an API. However, these can use the table as a sanity check. It’s extremely rare that divisibility changes.

Wrong divisibility means quantity off by a factor of 100M. Need to analyze what issues this can cause. Failed transactions only? Or loss of fund in certain situations?

TABLE INTEGRITY

Once the wallet is released and open-source, anyone who downloads and verifies the checksum (which includes the table too), should be confident that the table is correct. At least it should have been verified by multiple parties.

This unlike API which requires trust each time.

KEEPING TABLE UP TO DATE

A wallet with a local copy of the table will not have the latest assets. Also divisibility for older non-locked assets will be uncertain.

Updating the table would need an API, which in itself is as unsafe as relying on an API for every send. However, if several independent APIs are available, these can be cross checked, and it the table can updated safely that way.

Thinking about a bit more, a cleaner solution would be two tables;

1. Asset ID - Asset Longnames

SELECT asset_id, asset_longname FROM assets WHERE asset_longname IS NOT NULL

image

2. Locked Asset Divisibility

SELECT DISTINCT asset, divisible FROM issuances WHERE locked = 1 and status = "valid" ORDER BY tx_index

image

Both tables contain only the necessary info. Each row will never change. New rows will be added at the end.

A third table might be useful, where the divisibility of unlocked assets is indicated. A column with number of holders will be predictive of the likelihood of a reset. If the token is distributed, the probability of a reset is near zero.

This is a pretty interesting idea. We’re currently working through the best way to manage Counterparty ids in token.art app.

What is the algorithm to determine the asset_id?

From ID to name:

      function asset_name(id) {
        if (id == 0) return 'BTC';
        if (id == 1) return 'XCP';
        if (id >= 95428956661682177) return 'numerical or sub-asset';
        if (id > 9007199254740991) return 'max int error'; //a few very long asset names. would need bigint
        let b26_digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 
        let n = id;
        let name = '';
        do {
          let mod = n % 26;
          name = b26_digits[mod] + name;
          n -= mod;
          n /= 26;
        } while (n > 0);
        return name;
      }

I have the reverse function too. Will need to look for it.

This is great. Thanks @JPJA.
The reverse would definitely be helpful.

This one should work. Returns the ID has hex. The n_bigint variable should contain the ID.

function assetid(asset_name) {
    
    //asset_name.toUpperCase();

    if (asset_name == "XCP") {
        
        var asset_id = (1).toString(16);
        
    } else if (asset_name == "BTC") { 
        
        var asset_id = (0).toString(16);
    
    } else if (asset_name.substr(0, 1) == "A") {
        
        var pre_id = asset_name.substr(1);
        
        var pre_id_bigint = BigIntegerSM(pre_id);
        
        var asset_id = pre_id_bigint.toString(16);
          
    } else {  
    
        var b26_digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 
        var name_array = asset_name.split("");

        var n_bigint = BigIntegerSM(0);
    
        for (i = 0; i < name_array.length; i++) { 
            
            n_bigint = BigIntegerSM(n_bigint).multiply(26);
            n_bigint = BigIntegerSM(n_bigint).add(b26_digits.indexOf(name_array[i]));
                    
        }    

        var asset_id = n_bigint.toString(16);
    
    } 
    
    //return asset_id;
    console.log(asset_id);
    
    return asset_id;
    
}

Copied from my Electrum-Counterparty/transactions.js at master · Jpja/Electrum-Counterparty · GitHub which in turn is copied from Looney’s libraries.

An example:

JPJA’s ID is 168558 - see https://xchain.io/api/asset/jpja

From b26_digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' you get that
A = 0,
B = 1,

J = 9,

P = 15.

ID = 0*26 + 9*26^2 + 15*26^3 + 9*26^4 = 168558.

Awesome. Thanks!

assetid function updated.

  • uses BigInt (no library required)
  • returns by default hex. Optionally returns decimal if second parameter set to 10
  • error message if input is subasset name (must use the underlying numeric name)
      function assetid(asset_name, radix = 16) {
        if (asset_name.includes('.')) {
          console.log('Cannot get asset id from subasset. Use its numeric name.');
          return 'err';
        }
        if (asset_name == "XCP") {
          return (1).toString(radix);
        }
        if (asset_name == "BTC") { 
          return (0).toString(radix);
        } 
        if (asset_name.substr(0, 1) == "A") {
          return BigInt(asset_name.substr(1)).toString(radix);
        } 
        let b26_digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 
        let name_array = asset_name.split("");
        let n_bigint = BigInt(0);
        for (i = 0; i < name_array.length; i++) { 
          n_bigint *= BigInt(26);
          n_bigint += BigInt(b26_digits.indexOf(name_array[i]));
        }    
        return n_bigint.toString(radix);
      }
1 Like

Have you considered a p2p wallet?
I once stored Counterparty asset db in a variety of hyperbees (Hypercore Protocol).
Queried it as initial page in a forked Beaker Browser that I had wanted to make into a hybrid XCP wallet/web browser and resolve asset names as domains.