Resumen
- Datos mutables Vs. datos inmutables
- ATTRIBUTE_MAP
- Escribir datos mutables desde JavaScript
- Modificar datos mutables desde JavaScript
- Escribir datos mutables desde Smart Contract
- Modificar datos mutables desde Smart Contract
Datos mutables Vs. datos inmutables
Lo que hace que los NFTs se consideren activos digitales de valor es su capacidad para almacenar información de forma inalterable durante toda su existencia, además de su permanencia en un sistema descentralizado como es una blockchain. Gracias a la inalterabilidad de los datos inmutables es posible utilizar los NFTs como registros de propiedad o autoría infalsificables, algo muy apreciado por creadores digitales que buscan soluciones en su lucha contra la piratería, más concretamente en el uso fraudulento de las copias no autorizadas de sus trabajos.
Sin embargo, los NFTs también se han hecho muy populares en el mundo gaming ya que ofrecen la posibilidad a los jugadores de ser auténticos propietarios de los assets adquiridos en el juego y el poder conservarlos o comerciar con ellos más allá de la existencia del propio juego que les dio origen. Podríamos hablar, por ejemplo, de los coleccionistas de cartas que aún hoy guardan o intercambias cartas de juegos desaparecidos hace décadas. En los juegos de cartas digitales que comenzaron a sustituir a los tradicionales juegos de cartas físicas esto no podía hacerse ya que, si el juego desaparecía, tus cartas desaparecían con él. Ahora, gracias al uso de los NFTs es posible conservar las cartas digitales igualmente a como se conservan las cartas físicas. Mucha gente, sobre todo de cierta edad, considera que no es comparable, pero hay que tener en cuenta que las generaciones más jóvenes son totalmente digitales; casi todo el entretenimiento que consumen es digital, por lo que valoran tanto lo digital como nuestros mayores lo físico.
Pero para que un NFT pueda tener un uso pleno en un juego no es suficiente con que almacene de forma inalterable su información. De hecho, esto es algo contraproducente ya que es muy posible que el propio activo, una carta o un asset de juego como puede ser un arma o una pieza de armadura, necesite evolucionar con el tiempo y mejorar sus atributos para que pueda distinguirse de sus similares y obtener así un valor mayor. Y esto no sería posible si no tuvieran datos mutables.
Técnicamente no hay ninguna diferencia entre los datos mutables y los datos inmutables de un NFT; ambos son datos de información en el registro de la tabla de datos que ocupa el NFT en el interior de un smart contract. La diferencia reside en la lógica de la programación del smart contract el cual permite que los datos mutables puedan ser actualizados con nuevos datos mientras que impide la alteración de los datos inmutables.
Cierto que el smart contract podría ser modificado para revertir esta situación, pero si eso ocurriera, perdería por completo la confianza y dejaría de ser útil.
AtomicAssets es el smart contract más popular de WAX Blockchain (y otras EOSIO Blockchains) y está muy bien diseñado para su utilidad en los videojuegos por lo que dispone de datos inmutables y de datos mutables. Veremos a continuación cómo poder modificar esos datos.
Aquí vemos un ejemplo de un NFT (juego de cartas) con los datos inmutables a la izquierda y los datos mutables a la derecha. En este caso podemos deducir que el nivel de la carta (Lvl) podrá subir, así como sus atributos de fuerza, defensa y saqueo, entre otros.
Veamos ahora cómo modificarlos.
ATTRIBUTE_MAP
Antes de ver cómo se modifica un campo mutable desde JavaScrip es importante conocer el formato del objeto ATTRIBUTE_MAP. Una de las ventajas del estándar de AtomicAssets es el uso de serialización para ahorrar RAM en las tablas que contienen los datos de los NFTs. Para ello se utiliza un mapa de datos que asocia el tipo de dato con el contenido y así poder serializarlo.
El formato de este mapa se construye según este esquema:
[{"key": MYKEY, value: MYVALUE}, ...]
En la definición de la plantilla de un NFT se declaró en su momento los diferentes datos mutables e inmutables especificando el nombre del campo y el tipo de dato que contendrá. Así podemos tener el campo “name” de tipo string o el campo “speed” de tipo int(16 bits).
Si quisiéramos crear un mapa de datos para especificar esta información:
{ name: “Delorean”, speed: 300 }
Tendríamos que crear un array de objetos (teniendo en cuenta su declaración en la plantilla):
[{
“key”: “name”,
“value”: [“string”, “Delorean”]
}, {
“key”: “speed”,
“value”: [“uint16”, 300]
}]
Veamos ahora cómo hacerlo desde JavaScript
Escribir datos mutables en los NFTs desde JavaScript
Para modificar los datos mutables de un NFT de AtomicAssets desde JavaScript podemos utilizar la librería eosjs. Puedes ver algunos ejemplos de su uso en:
Para este caso tendremos que llamar a la acción “setassetdata” del propio smart contract de AtomicAssets. Esta acción requiere de 4 parámetros:
- Authorized_editor: Nombre de cuenta que debe de estar autorizado en la colección a la que pertenece el NFT.
- Asset_owner: Nombre de la cuenta que posee el asset en este momento (para cambiar un dato mutable no es necesario estar en posesión del asset, solo se necesita estar autorizado para hacerlo o ser su creador)
- Asset_id: Número ID del NFT a modificar.
- New_mutable_data: estructura de tipo ATTRIBUTE_MAP que contiene la información a modificar (como ya vimos antes)
Siguiendo con el ejemplo anterior podríamos definir una función que reciba la información necesaria como parámetros y se encargue de llamar al smart contract de “atomicassets”:
Const updateNft = (authorized, owner, asset_id, name, speed) => {
apiRpc.transact({
actions: [{
account: 'atomicassets',
name: 'setassetdata',
authorization: [{
actor: authorized,
permission: 'active'
}],
data: {
authorized_editor: authorized,
asset_owner: owner,
asset_id: asset_id,
new_mutable_data: [{
'key': 'name',
'value': ["string", name]
},
{
'key': 'speed',
'value': ['uint16', speed]
}]
}
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
}).then(result => {
log(result);
}).catch(err => {
console.log(err, JSON.stringify(err, null, 2));
});
}
Modificar datos mutables desde JavaScript
Hemos visto cómo introducir directamente un dato mutable en un NFT, pero en ocasiones necesitaremos actualizar su contenido. Por ejemplo, sumar una cantidad de puntos a la puntuación existente.
Obviamente necesitaremos leer primero el contenido del campo, pero hay que tener en cuenta que el dato está serializado, como ya mencionamos antes. Si hacemos uso de la librería eosjs tendremos que realizar la engorrosa tarea de deserializar la información primero. Afortunadamente, el equipo de Pink Network, creadores del estándar AtomicAssets, han creado también una librearía JavaScript para acceder a toda la información de las colecciones, esquemas, plantillas y, por supuesto, NFTs, de una manera mucho más cómoda.
https://www.npmjs.com/package/atomicassets
Del mismo modo que la librería eosjs requiere conectarse a un API Full Story de WAX Blockchain, la librería de AtomicAssets necesita conectarse a un API específico de Atomic. Por fortuna son varios los Productores de Bloques de WAX que ofrecen este tipo de servicio de forma gratuita a la comunidad de usuarios de WAX Blockchain.
En este ejemplo podemos ver cómo conectarnos a un API para Atomic, leer un NFT según su ID y cómo obtener el contenido de uno de sus datos
const { ExplorerApi } = require("atomicassets");
const api = new ExplorerApi(
"https://atomic.3dkrender.com",
"atomicassets",
{ fetch });
const asset = await api.getAsset(asset_id);
let player_points = asset.mutable_data.player_points;
player_points += 100;
Una vez leído ya solo queda actualizar la información con los valores deseados y volver a grabarla en el NFT como ya vimos en el paso anterior.
Escribir datos mutables de los NFTs de AtomicAssets desde un smart contract
Para operar con los NFTs de AtomicAssets desde nuestro smart contract deberemos incluir en nuestro código algunos archivos del smart contract ‘atomicassets’ para poder reutilizar ciertas definiciones y declaraciones de datos y funciones. Concretamente necesitamos los archivos:
- atomicassets.hpp
- atomicdata.hpp
- base58.hpp
que los podremos obtener desde el GitHub de Pink Network (https://github.com/pinknetworkx/atomicassets-contract/tree/master/include)
El primer paso será declarar un objeto de tipo ATTRIBUTE_MAP
atomicassets::ATTRIBUTE_MAP mdata = {};
Y ya podemos comenzar a añadirle datos:
mdata[“name”] = string(“Delorean”);
mdata[“speed”] = uint16_t(300);
O, si estamos recibiendo los datos como parámetros de una función (véase ejemplo JavaScript)
mdata[“name”] = name;
mdata[“speed”] = speed;
Y ya solo queda llamar a la acción del smart contract de ‘atomicassets’ para realizar la operación:
action(
permission_level{name(“authorized”), name("active")},
name(“atomicassets”),
name("setassetdata"),
make_tuple(name(“authorized”), name(“owner”), asset_id, mdata))
.send();
Modificar datos mutables desde el smart contract
Nos encontramos en una situación similar a la vista desde JavaScript, pero en esta ocasión sí que será necesario que realicemos el proceso de deserialización de los datos. Por fortuna, Pink Network también nos facilitará el trabajo (es el motivo por el que importamos los archivos “atomicdata” y “base58” en el paso anterior.
En primer lugar, deberemos localizar el NFT y para ello accederemos a la tabla de assets en posesión del usuario que lo tiene en este momento. Ahora es cuando vemos la importancia que tiene conocer quién tiene en su wallet el NFT ya que será el scope de la tabla de datos de los assets de AtomicAssets.
La información necesaria para serializar y deserializar los datos (nombres de objetos y tipo de datos) está almacenada en el esquema de la colección por lo que también deberemos acceder a la información del esquema al que pertenece el NFT en questión.
// Leer assets de owner
atomicassets::assets_t listAssets = atomicassets::get_assets(owner);
// Buscar el NFT según su ID
auto idxCard = listAssets.find(asset_id);
// Acceder a la tabla de esquemas de la colección del asset
atomicassets::schemas_t collection_schemas = atomicassets::get_schemas(idxCard->collection_name);
// obtener la información del esquema del asset para conocer los tipos de datos
auto idxSchema = collection_schemas.find(idxCard->schema_name.value);
// Deserializar los datos
mdata = atomicdata::deserialize(
idxCard->mutable_serialized_data,
idxSchema->format);
// Acceder al dato que queremos cambiar
uint32_t mdata_player_points = get<uint32_t>(mdata["player_points"]);
// y actualizar
mdata_player_points += player_points;
Una vez obtenido el dato ya podemos operar con él y almacenarlo de nuevo como vimos en el ejemplo anterior. Solo debemos tener en cuenta que la función “deserialize” nos devuelve un objeto de tipo ATTRIBUTE_MAP por lo que podremos utilizarlo directamente en la operación de escritura.
Contacto:
Espero que esta publicación sea de ayuda para tus proyectos. Si te ha gustado no dudes en darme tu apoyo con un voto como Productor de Bloques en WAX Blockchain.
https://wax.bloks.io/vote?producers=3dkrenderwax
Twitter: https://twitter.com/MarcoS3DK
YouTube: https://youtube.com/MarcosDK
Blog: https://3dkrender.com
Discord: https://discord.gg/3dkrender