Legal Officer Case (LOC)
A LOC is an encrypted and decentralized digital folder containing public and private data. Its content is reviewed and signed by a Logion Legal Officer (LLO), an individual operating under a strict legal framework.
This section is about how to manager your LOCs.
State
An authenticated client is necessary for all LOC operations.
The global state of LOCs can be obtained (and later on, refreshed) with:
const locsState = await client.locsState();
const refreshedState = await locsState.refresh();
All user operations (requestTransactionLoc, requestCollectionLoc, etc.), as well as refresh, do return a new state.
Always use the most recent state, and discard the former state.
In the example above, the var locsState must not be used any more as soon as refreshedState is available.
Lifecycle
In below diagram, replace ___ by a LOC type. The process is the same for all LOC types.

Identity LOC
Request
An Identity LOC is requested this way:
const draftRequest = await locsState.requestIdentityLoc({
    legalOfficer: alice,
    description: "This is an Identity LOC",
    userIdentity: {
        email: "john.doe@invalid.domain",
        firstName: "John",
        lastName: "Doe",
        phoneNumber: "+1234",
    },
    userPostalAddress: {
        line1: "Peace Street",
        line2: "2nd floor",
        postalCode: "10000",
        city: "MyCity",
        country: "Wonderland"
    },
    draft: true,
    legalFee: Lgnt.from(160),
});
The default legal fee for an identity
LOC is 160 LGNTs. Another value of legalFee should have been discussed with the LLO beforehand,
otherwise it may reject the LOC.
Add items
You may add metadata (i.e. public name-value pairs), private files and links (to other LOCs) to your LOC.
Metadata can be added to, or removed from, a draft LOC:
draftRequest = await draftRequest.addMetadata({
    name: "Some name",
    value: "Some value"
});
draftRequest = await draftRequest.deleteMetadata({
    name: "Some name"
});
Submit
When done, the request must be submitted to the LLO for review:
const pendingRequest = await draftRequest.submit();
After review
If the LLO accepted your LOC, you can proceed and publish it:
const acceptedRequest = await pendingRequest.refresh() as AcceptedRequest;
const openLoc = await acceptedRequest.open({ signer, autoPublish: true });
If you set autoPublish to false, you'll have to manually publish each item
manually after the call to open.
After everything has been published, just wait for the LLO to close the LOC:
const closedLoc = await openLoc.refresh() as ClosedLoc;
Transaction LOC
Request
const draftRequest = await locsState.requestTransactionLoc({
    legalOfficer: alice,
    description: "This is a Transaction LOC",
    draft: true,
    legalFee: Lgnt.from(2000),
});
The default legal fee for a transaction
LOC is 2000 LGNTs. Another value of legalFee should have been discussed with the LLO beforehand,
otherwise the LLO may reject the LOC.
Collection LOC
Request
When requesting a Collection LOC, additional parameters have to be provided.
Note that at least one of lastBlockSubmission and maxSize have to be defined.
const draftRequest = await locsState.requestCollectionLoc({
    legalOfficer: alice,
    description: "This is a Collection LOC",
    draft: true,
    legalFee: Lgnt.from(2000),
    valueFee: Lgnt.zero(),
    collectionItemFee: Lgnt.zero(),
    tokensRecordFee: Lgnt.zero(),
    collectionParams: {
        lastBlockSubmission: 100000n,
        maxSize: 9999,
        canUpload: true,
    }
});
The default legal fee for a collection LOC is 2000 LGNTs. Another value
of legalFee should be discussed with the LLO beforehands,
this also applies to valueFee, collectionItemFee, tokensRecordFee and the collection
parameters. Otherwise the LLO may reject the LOC.
Direct LOC opening
In order to bypass the review process, one can directly create and publish a LOC and its items in a single call and signature:
const openLoc = await locsState.openIdentityLoc({
    description: "This is an Identity LOC",
    legalOfficerAddress: alice.address,
    files,
    metadata,
    links,
    userIdentity: {
        email: "john.doe@invalid.domain",
        firstName: "John",
        lastName: "Doe",
        phoneNumber: "+1234",
    },
    userPostalAddress: {
        line1: "Peace Street",
        line2: "2nd floor",
        postalCode: "10000",
        city: "MyCity",
        country: "Wonderland"
    },
    signer
});
Methods openTransactionLoc and openCollectionLoc can also be used. openCollection will
require the additional fields (see requestCollectionLoc).
You have to make sure that the LLO agrees to close the LOCs directly opened this way. If he doesn't, he may never close or even void your LOC. In that case, you will "lose" (i.e. there is no refund) the LGNTs paid so far for the creation of the LOC.
Queries
Querying requests
Pending and rejected requests can be queried:
const type: LocType = 'Transaction'; // could be 'Collection' or 'Identity'
const pendingRequests = locsState.pendingRequests[type];
const rejectedRequests = locsState.rejectedRequests[type];
Querying LOCs
Similarly, LOC's can be queried according to their state:
const type: LocType = 'Transaction'; // could be 'Collection' or 'Identity'
const openLocs = locsState.openLocs[type];
const closedLocs = locsState.closedLocs[type];
const voidedLocs = locsState.voidedLocs[type];
Accessing a LOC's data
const locData: LocData = locsState.openLocs["Identity"][0].data();
const userIdentity = locData.userIdentity; // Only set on identity LOCs
console.log("Identity of %s: %s %s %s %s", 
    locData.requesterAddress, 
    userIdentity?.firstName, 
    userIdentity?.lastName, 
    userIdentity?.email, 
    userIdentity?.phoneNumber
);
Collection Items
When creating a Collection LOC, the collection parameters tell if it supports file upload or not.
Collection WITHOUT upload support
Add an item to the collection:
const itemId = Hash.of("first-collection-item");
const itemDescription = "First collection item";
closedLoc = await closedLoc.addCollectionItem({
    payload: {
        itemId,
        itemDescription,
    },
    signer,
});
Later on, you can retrieve the item with its ID:
const item = await closedLoc.getCollectionItem({ itemId });
Collection WITH upload support
A collection item may have attached files if the collection permits it. The files are then stored in logion's private IPFS network ensuring their availability over time. If a controlled delivery for attached files is needed, see "Collection with restricted delivery" below.
There are 2 possibilities when attaching files to an item:
- immediate upload of the files upon item creation or
- item creation followed by an explicit upload later on.
See the examples below.
const itemId = Hash.of("first-collection-item");
const itemDescription = "First collection item";
closedLoc = await closedLoc.addCollectionItem({
    payload: {
        itemId,
        itemDescription,
        itemFiles: [
            HashOrContent.fromContent(
                new NodeFile("integration/test.txt", "test.txt", MimeType.from("text/plain"))
            ), // Let SDK compute hash and size
        ],
    },
    signer,
});
const itemId = Hash.of("first-collection-item");
const itemDescription = "First collection item";
const file = HashOrContent.fromContentFinalized(
    new NodeFile("integration/test.txt", "test.txt", MimeType.from("text/plain"))
);
closedLoc = await closedLoc.addCollectionItem({
    payload: {
        itemId,
        itemDescription,
        itemFiles: [
            HashOrContent.fromDescription({
                name: file.name,
                hash: file.contentHash,
                size: file.size,
                mimeType: file.mimeType,
            }),
        ]
    },
    signer,
});
closedLoc = await closedLoc.uploadCollectionItemFile({
    itemId,
    itemFile: file,
});
Collection with restricted delivery
A collection item with restricted delivery requires a token definition i.e. the "address" of the token which opens access to the underlying files when owned. Below an example where the underlying files will be delivered to the owner of an ERC-721 token on Ethereum Mainnet.
const itemId = generateEthereumTokenItemId("202210131750", "4391");
const itemDescription = "First collection item";
const itemToken: ItemTokenWithRestrictedType = {
    type: "ethereum_erc721",
    id: '{"contract":"0x765df6da33c1ec1f83be42db171d7ee334a46df5","id":"4391"}'
};
const file = HashOrContent.fromContent(
    new NodeFile("integration/test.txt", "test.txt", MimeType.from("text/plain"))
);
closedLoc = await closedLoc.addCollectionItem({
    payload: {
        itemId: firstItemId,
        itemDescription: firstItemDescription,
        itemFiles: [ file ],
        itemToken,
        restrictedDelivery: true,
    },
    signer,
});
In the above example, the item ID is generated using function generateEthereumTokenItemId. This ensures that the item ID
matches the one computed by the Logion Smart Contract.
This is very important because otherwise, the bidirectional link between the item and its token would be broken.
The nonce parameter must match the one in the Smart Contract.
One may consider not using the Logion Smart Contract, leaving the choice of the item ID completely open, but this is not recommended.
This is the list of supported token types:
- ethereum_erc721
- ethereum_erc1155
- goerli_erc721
- goerli_erc1155
- singular_kusama
- polygon_erc721
- polygon_erc1155
- polygon_mumbai_erc721
- polygon_mumbai_erc1155
- ethereum_erc20
- goerli_erc20
- polygon_erc20
- polygon_mumbai_erc20
- multiversx_devnet_esdt
- multiversx_testnet_esdt
- multiversx_esdt
- astar_psp34
- astar_shiden_psp34
- astar_shibuya_psp34
The format of id field depends on the token type:
- With types astar_*_psp34,*_erc721and*_erc1155, theidfield must be a valid JSON object with 2 fields:contractandid. Both fields are strings:- contractis the address of the Smart Contract of the token.
- idis the token ID as assigned by the Smart Contract.
 
- With types *_erc20, theidfield must be a valid JSON object with a singlecontractfields:- contractis the address of the Smart Contract of the token.
 
- With multiversx_*types,idis a string representing the ID of an ESDT token.
Terms and Conditions
Terms and conditions can be added to the collection item, using either the Logion classification,
a set of SpecificLicenses, or both.
If using a specific license, a valid closed Transaction LOC defining the license must exist
(referred as specificLicenseLocId in the example below).
const itemId = Hash.of("first-collection-item");
const itemDescription = "First collection item";
const file = HashOrContent.fromContent(
    new NodeFile("integration/test.txt", "test.txt", MimeType.from("text/plain"))
);
closedLoc = await closedLoc.addCollectionItem({
    payload: {
        itemId,
        itemDescription,
        itemFiles: [ file ],
        logionClassification: {
            regionalLimit: [ "BE", "FR", "LU" ],
            transferredRights: [ "PER-PRIV", "REG", "TIME" ],
            expiration: "2025-01-01",
        },
        specificLicenses: [
            new SpecificLicense(specificLicenseLocId, "Some details about the license"),
        ]
    },
    signer,
});
- The Logion Classification allows to define a set of transferredRightsto define precisely the scope of the terms and conditions. All possible transferred rights are available in thelogionLicenseItemsarray.
- With the code TIMEit is possible to limit the right in time by setting the parameterexpiration.
- With the code REGit is possible to limit to some countries/regions with the parameterregionalLimit.
Tokens Records
A tokens record has attached files. The files are then stored in Logion's private IPFS network ensuring their availability over time.
As for collection items, there are 2 possibilities when attaching files:
- immediate upload of the files on creation or
- creation followed by an explicit upload later on.
See the examples below.
const recordId = Hash.of("record-id");
const recordDescription = "Some tokens record";
const file = HashOrContent.fromContent(
    new NodeFile("integration/test.txt", "report.txt", MimeType.from("text/plain"))
);
closedLoc = await closedLoc.addTokensRecord({
    payload: {
        recordId,
        description: recordDescription,
        files: [ file ],
    },
    signer,
});
const recordId = Hash.of("record-id");
const recordDescription = "Some tokens record";
const file = HashOrContent.fromContentFinalized(
    new NodeFile("integration/test.txt", "report.txt", MimeType.from("text/plain"))
);
closedLoc = await closedLoc.addTokensRecord({
    payload: {
        recordId,
        description: recordDescription,
        files: [
            HashOrContent.fromDescription({
                name: file.name,
                hash: file.contentHash,
                size: file.size,
                mimeType: file.mimeType,
            }),
        ],
    },
    signer,
});
closedLoc = await closedLoc.uploadTokensRecordFile({
    recordId,
    file,
});