Skip to content

IPFS Realms

Nicolas Lorusso edited this page Feb 7, 2024 · 4 revisions

Publish an entity

Catalysts in Decentraland follows IPFS protocol either for getting or storing information, thus the client must follow it. In order to do so, it is needed:

Here is an example on how to publish a profile.

using IpfsProfileEntity = DCL.Ipfs.EntityDefinitionGeneric<DCL.Profiles.GetProfileJsonRootDto>;

private readonly IRealmData realm;
private readonly Dictionary<string, byte[]> files = new ();

public PublishProfileExample(IRealmData realm)
{
    this.realm = realm;
}

public async UniTask SetAsync(Profile profile, CancellationToken ct)
{
    IIpfsRealm ipfs = realm.Ipfs;

    // Get face & body png textures somewhere, but will be deprecated in the future in the catalysts
    byte[] faceSnapshotTextureFile = new byte[...];
    byte[] bodySnapshotTextureFile = new byte[...];

    string faceHash = ipfs.GetFileHash(faceSnapshotTextureFile);
    string bodyHash = ipfs.GetFileHash(bodySnapshotTextureFile);

    using var profileDto = GetProfileJsonRootDto.Create();
    profileDto.CopyFrom(profile);
    profileDto.avatars[0].avatar.snapshots.body = bodyHash;
    profileDto.avatars[0].avatar.snapshots.face256 = faceHash;

    var entity = new IpfsProfileEntity
    {
        version = IpfsProfileEntity.DEFAULT_VERSION,
        content = new List<ContentDefinition>
        {
            new () { file = "body.png", hash = faceHash },
            new () { file = "face256.png", hash = bodyHash },
        },
        pointers = new List<string> { profile.UserId },
        timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
        type = IpfsRealmEntityType.Profile.ToEntityString(),
        metadata = profileDto,
    };

    files.Clear();
    // Add face256 & body snapshots as extra content files, since its required when uploading the profile in the catalysts at the moment, but will be deprecated in the future
    files[bodyHash] = bodySnapshotTextureFile;
    files[faceHash] = faceSnapshotTextureFile;

    try
    {
        await ipfs.PublishAsync(entity, ct, files);
    }
    finally
    {
        files.Clear();
    }
}

First we need to build the entity structure. Some fields worth mentioning:

  • metadata: the custom data that will be stored as part of the entity, in this case a GetProfileJsonRootDto.
  • type: allowed types scene, profile, wearable, store, emote, outfits.
  • pointers: the ids used as a reference for further entity retrieval, in this case the profile id.
  • content: its optional, thus could be empty. This applies for entities that contains extra related files, in this case the body and face snapshots of the avatar. Consists in the file original name and the hashed content of the file, generally the result of: fileBytes.IpfsHashV1(), but encapsulated into ipfs.GetFileHash(fileBytes).

Internally, when doing:

await ipfs.PublishAsync(entity, ct, files);

The protocol converts the entity into a json file and then calculates its hashV1 to get the entityId, which will be later used to construct the request. After the entity id is solved, adds the auth chain information by signing the entityId with the ephemeral address and finally appends the binary content files. The resultant HTTP request is something like this:

curl --location 'https://peer.decentraland.org/content/entities' \
--form 'entityId="${the hashV1 resolved from the entity object}"' \
--form 'authChain[0][type]="SIGNER"' \
--form 'authChain[0][payload]="${your current web3 identity's address}"' \
--form 'authChain[0][signature]=""' \
--form 'authChain[1][type]="ECDSA_EPHEMERAL"' \
--form 'authChain[1][payload]="Decentraland Login
Ephemeral address: ${web3 identity ephemeral address}
Expiration: ${web3 identity ephemeral expiration}"' \
--form 'authChain[1][signature]="${web3 identity signature}"' \
--form 'authChain[2][type]="ECDSA_SIGNED_ENTITY"' \
--form 'authChain[2][payload]="${the entityId}"' \
--form 'authChain[2][signature]="${the signed entityId generated by the ephemeral web3 identity}"' \
--form '${face256FileHashV1}=@"${face256FileHashV1}"' \
--form '${bodyFileHashV1}=@"${bodyFileHashV1}"' \
--form '${entityId}=@"${entityId}"'
Clone this wiki locally