diff --git a/packages/client/package.json b/packages/client/package.json index efd27999..a3c23ec8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@logion/client", - "version": "0.45.0-11", + "version": "0.45.0-12", "description": "logion SDK for client applications", "main": "dist/index.js", "packageManager": "yarn@3.2.0", diff --git a/packages/client/src/LocClient.ts b/packages/client/src/LocClient.ts index d6c30b02..15e4725e 100644 --- a/packages/client/src/LocClient.ts +++ b/packages/client/src/LocClient.ts @@ -303,6 +303,7 @@ export interface AddTokensRecordParams { recordId: Hash, description: string, files: HashOrContent[], + chargeSubmitter?: boolean, } export interface GetTokensRecordDeliveriesRequest { @@ -1399,6 +1400,7 @@ export class AuthenticatedLocClient extends LocClient { description, locId, files, + chargeSubmitter, } = parameters; const chainItemFiles: TypesTokensRecordFile[] = []; @@ -1419,6 +1421,7 @@ export class AuthenticatedLocClient extends LocClient { this.nodeApi.adapters.toH256(recordId), this.nodeApi.adapters.toH256(Hash.of(description)), this.nodeApi.adapters.newTokensRecordFileVec(chainItemFiles), + chargeSubmitter || false, ); } diff --git a/packages/client/test/Loc.spec.ts b/packages/client/test/Loc.spec.ts index c821e2d9..2ac4d812 100644 --- a/packages/client/test/Loc.spec.ts +++ b/packages/client/test/Loc.spec.ts @@ -525,7 +525,7 @@ describe("ClosedCollectionLoc", () => { }); signer.verify(instance => instance.signAndSend(It.IsAny()), Times.Once()); - nodeApiMock.verify(instance => instance.polkadot.tx.logionLoc.addTokensRecord(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); + nodeApiMock.verify(instance => instance.polkadot.tx.logionLoc.addTokensRecord(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), false), Times.Once()); }); it("uploads tokens record file", async () => { @@ -564,7 +564,7 @@ describe("ClosedCollectionLoc", () => { }); signer.verify(instance => instance.signAndSend(It.IsAny()), Times.Once()); - nodeApiMock.verify(instance => instance.polkadot.tx.logionLoc.addTokensRecord(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + nodeApiMock.verify(instance => instance.polkadot.tx.logionLoc.addTokensRecord(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), false), Times.Exactly(2)); }); it("can be voided", async () => { @@ -1188,6 +1188,7 @@ async function buildSharedState(isVerifiedIssuer: boolean = false): Promise(itemId => itemId.toHex() !== ITEM_ID.toHex()), ITEM_DESCRIPTION, It.IsAny(), + false, )).returns(addTokensRecordExtrinsic.object()); nodeApiMock.setup(instance => instance.polkadot.query.logionLoc.invitedContributorsByLocMap.entries(It.IsAny())).returns(Promise.resolve([])) diff --git a/packages/node-api/integration/InvitedContributors.ts b/packages/node-api/integration/InvitedContributors.ts index 1ea7018b..57f4e2e2 100644 --- a/packages/node-api/integration/InvitedContributors.ts +++ b/packages/node-api/integration/InvitedContributors.ts @@ -75,6 +75,7 @@ export async function invitedContributors() { hash: recordFileHash, } ]), + false, )); const record = Adapters.toTokensRecord((await api.polkadot.query.logionLoc.tokensRecordsMap(collectionLocId.toDecimalString(), recordId)).unwrap()); diff --git a/packages/node-api/integration/VerifiedIssuers.ts b/packages/node-api/integration/VerifiedIssuers.ts index f5d19f93..dc3b1a17 100644 --- a/packages/node-api/integration/VerifiedIssuers.ts +++ b/packages/node-api/integration/VerifiedIssuers.ts @@ -135,6 +135,7 @@ export async function verifiedIssuers() { hash: recordFileHash, } ]), + false, )); const record = Adapters.toTokensRecord((await api.polkadot.query.logionLoc.tokensRecordsMap(collectionLocId.toDecimalString(), recordId)).unwrap()); diff --git a/packages/node-api/package.json b/packages/node-api/package.json index e1e59a47..b60f3c37 100644 --- a/packages/node-api/package.json +++ b/packages/node-api/package.json @@ -1,6 +1,6 @@ { "name": "@logion/node-api", - "version": "0.30.0", + "version": "0.31.0-1", "description": "logion API", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/packages/node-api/src/interfaces/augment-api-tx.ts b/packages/node-api/src/interfaces/augment-api-tx.ts index c784554c..72a2351e 100644 --- a/packages/node-api/src/interfaces/augment-api-tx.ts +++ b/packages/node-api/src/interfaces/augment-api-tx.ts @@ -203,7 +203,7 @@ declare module '@polkadot/api-base/types/submittable' { /** * See [`Pallet::add_tokens_record`]. **/ - addTokensRecord: AugmentedSubmittable<(collectionLocId: Compact | AnyNumber | Uint8Array, recordId: H256 | string | Uint8Array, description: H256 | string | Uint8Array, files: Vec | (PalletLogionLocTokensRecordFile | { name?: any; contentType?: any; size_?: any; hash_?: any } | string | Uint8Array)[]) => SubmittableExtrinsic, [Compact, H256, H256, Vec]>; + addTokensRecord: AugmentedSubmittable<(collectionLocId: Compact | AnyNumber | Uint8Array, recordId: H256 | string | Uint8Array, description: H256 | string | Uint8Array, files: Vec | (PalletLogionLocTokensRecordFile | { name?: any; contentType?: any; size_?: any; hash_?: any } | string | Uint8Array)[], chargeSubmitter: bool | boolean | Uint8Array) => SubmittableExtrinsic, [Compact, H256, H256, Vec, bool]>; /** * See [`Pallet::close`]. **/ diff --git a/packages/node-api/src/interfaces/lookup.ts b/packages/node-api/src/interfaces/lookup.ts index d771ee96..99856d1c 100644 --- a/packages/node-api/src/interfaces/lookup.ts +++ b/packages/node-api/src/interfaces/lookup.ts @@ -2846,6 +2846,7 @@ export default { recordId: 'H256', description: 'H256', files: 'Vec', + chargeSubmitter: 'bool', }, create_other_identity_loc: { locId: 'Compact', diff --git a/packages/node-api/src/interfaces/types-lookup.ts b/packages/node-api/src/interfaces/types-lookup.ts index 337f0b01..5bc17e1d 100644 --- a/packages/node-api/src/interfaces/types-lookup.ts +++ b/packages/node-api/src/interfaces/types-lookup.ts @@ -3072,6 +3072,7 @@ declare module '@polkadot/types/lookup' { readonly recordId: H256; readonly description: H256; readonly files: Vec; + readonly chargeSubmitter: bool; } & Struct; readonly isCreateOtherIdentityLoc: boolean; readonly asCreateOtherIdentityLoc: { diff --git a/website/docs/client/items.md b/website/docs/client/items.md new file mode 100644 index 00000000..6de7cffb --- /dev/null +++ b/website/docs/client/items.md @@ -0,0 +1,205 @@ +--- +sidebar_position: 5 +description: How to manage collection items. +--- + +# Collection Items + +A Collection Item may link tokens issued on some blockchain (see [below](#collection-with-restricted-delivery) for a list of supported chains) +to a closed Collection LOC. This way, the tokens are bound to some verified documentation +(e.g. the legal framework). + +When creating a Collection LOC, the collection parameters tell if it supports file upload or not. + +## State + +:::note +A [closed Collection LOC](loc.md#collection-loc) is required. +::: + +## Collection WITHOUT upload support + +Add an item to the collection: + +```typescript title="Add Item" +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: + +```typescript title="Get an Item" +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. + +```typescript title="Add Item and provide file content" +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, +}); +``` + +```typescript title="Add Item and provide hash and size" +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, +}); +``` + +```typescript title="Upload file for an existing item" +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. + +```typescript title="Add Item with restricted delivery" +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, +}); +``` + +:::danger +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](https://github.com/logion-network/logion-solidity/blob/main/contracts/Logion.sol). +**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`, `*_erc721` and `*_erc1155`, the `id` field must be a valid JSON object with 2 fields: `contract` and `id`. Both fields are strings: + - `contract` is the address of the Smart Contract of the token. + - `id` is the token ID as assigned by the Smart Contract. +- With types `*_erc20`, the `id` field must be a valid JSON object with a single `contract` fields: + - `contract` is the address of the Smart Contract of the token. +- With `multiversx_*` types, `id` is 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 `SpecificLicense`s, or both. + +If using a specific license, a valid closed Transaction LOC defining the license must exist +(referred as `specificLicenseLocId` in the example below). + +```typescript +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, +}); +``` + +:::note Logion Classification +* The Logion Classification allows to define a set of `transferredRights` to define precisely the scope of the terms and conditions. +All possible transferred rights are available in the `logionLicenseItems` array. +* With the code `TIME` it is possible to limit the right in time by setting the parameter `expiration`. +* With the code `REG` it is possible to limit to some countries/regions with the parameter `regionalLimit`. +::: diff --git a/website/docs/client/loc.md b/website/docs/client/loc.md index b7c011a3..3a5288d2 100644 --- a/website/docs/client/loc.md +++ b/website/docs/client/loc.md @@ -165,6 +165,8 @@ this also applies to `valueFee`, `collectionItemFee`, `tokensRecordFee` and the parameters. Otherwise the LLO may reject the LOC. ::: +After a Collection LOC was closed, it may be enriched by adding [Collection Items](./items.md) and/or [Tokens Records](./records.md). + ## Direct LOC opening In order to bypass the review process, one can directly create and publish a LOC and its @@ -237,251 +239,3 @@ console.log("Identity of %s: %s %s %s %s", 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: - -```typescript title="Add Item" -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: - -```typescript title="Get an Item" -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. - -```typescript title="Add Item and provide file content" -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, -}); -``` - -```typescript title="Add Item and provide hash and size" -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, -}); -``` - -```typescript title="Upload file for an existing item" -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. - -```typescript title="Add Item with restricted delivery" -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, -}); -``` - -:::danger -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](https://github.com/logion-network/logion-solidity/blob/main/contracts/Logion.sol). -**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`, `*_erc721` and `*_erc1155`, the `id` field must be a valid JSON object with 2 fields: `contract` and `id`. Both fields are strings: - - `contract` is the address of the Smart Contract of the token. - - `id` is the token ID as assigned by the Smart Contract. -- With types `*_erc20`, the `id` field must be a valid JSON object with a single `contract` fields: - - `contract` is the address of the Smart Contract of the token. -- With `multiversx_*` types, `id` is 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 `SpecificLicense`s, or both. - -If using a specific license, a valid closed Transaction LOC defining the license must exist -(referred as `specificLicenseLocId` in the example below). - -```typescript -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, -}); -``` - -:::note Logion Classification -* The Logion Classification allows to define a set of `transferredRights` to define precisely the scope of the terms and conditions. -All possible transferred rights are available in the `logionLicenseItems` array. -* With the code `TIME` it is possible to limit the right in time by setting the parameter `expiration`. -* With the code `REG` it is possible to limit to some countries/regions with the parameter `regionalLimit`. -::: - -## 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. - -```typescript title="Add record and provide file content" -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, -}); -``` - -```typescript title="Add record and provide description only" -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, -}); -``` - -```typescript title="Upload file for an existing record" -closedLoc = await closedLoc.uploadTokensRecordFile({ - recordId, - file, -}); -``` diff --git a/website/docs/client/protection.md b/website/docs/client/protection.md index db052451..4cc93c5b 100644 --- a/website/docs/client/protection.md +++ b/website/docs/client/protection.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 7 description: How to protect/recover a Polkadot account, and access the vault. --- diff --git a/website/docs/client/records.md b/website/docs/client/records.md new file mode 100644 index 00000000..3ac489bb --- /dev/null +++ b/website/docs/client/records.md @@ -0,0 +1,89 @@ +--- +sidebar_position: 6 +description: How to manage tokens records. +--- + +# Tokens Records + +Tokens records may be added to a closed Collection LOC in order to provide some update +on the subject of the LOC (e.g. annual revenues generated by the loans of a tokenized +building). + +A tokens record has attached files. The files are 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](#examples). + +## State + +:::note +A [closed Collection LOC](loc.md#collection-loc) is required. +::: + +## Examples + +```typescript title="Add record and provide file content" +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, +}); +``` + +```typescript title="Add record and provide description only" +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, +}); +``` + +```typescript title="Upload file for an existing record" +closedLoc = await closedLoc.uploadTokensRecordFile({ + recordId, + file, +}); +``` + +When adding a Tokens Record, the submitter may decide to pay all fees (not only inclusion fees) instead of the requester. +To do so, set `chargeSubmitter` flag to true. + +```typescript title="Add record and charge submitter for all fees" +... +closedLoc = await closedLoc.addTokensRecord({ + payload: { + recordId, + description: recordDescription, + files: [ file ], + chargeSubmitter: true, + }, + signer, +}); +``` diff --git a/website/docs/client/secrets.md b/website/docs/client/secrets.md new file mode 100644 index 00000000..4c70fc9b --- /dev/null +++ b/website/docs/client/secrets.md @@ -0,0 +1,80 @@ +--- +sidebar_position: 8 +description: How to manage Recoverable Secrets. +--- + +# Recoverable Secrets + +A Recoverable Secret (or Secret) is a key-value pair a user can attach to one of its closed Identity LOCs. +The Recoverable Secret may be recovered later, even if the user loses access to its keypair. +This is achieved by submitting a Secret Recovery Request which will be reviewed by a Logion Legal Officer (LLO). +If the request is accepted by the LLO, following some kind of KYC process, the user will be able to retrieve the secret’s value. + +It is not recommended to use "clear-text" secret values. Even if the probability is quite low, it is never +impossible that the secret's value gets leaked. Therefore, methods like sharding or encryption should be applied +to the "master" secret in order to produce several derived secrets. + +## State + +:::note +A [closed Identity LOC](loc.md#identity-loc) is required to add or remove secrets. +In order to recover a secret, an unauthenticated client is enough. +::: + +## Add a Recoverable Secret + +```typescript +closedLoc = await closedLoc.addSecret({ + name: "Encrypted keypair", + value: "YLS+hsOaYqPdJLoTqHCg/muhOemq2kr6weHCi+iSWoafNbynSU7gUCy+k2O6WKyQ1NwnajNupaqhdYI+9dCdPpmi/6OSj39/FuzandUFOZ5tlyH/z9kUE7Wqfl4/tR07", +}); +``` + +A tool like [`logion-secrets`](https://github.com/logion-network/logion-tools/blob/main/packages/secrets/README.md) could be used to +generate the secret's value. + +## Remove a Recoverable Secret + +```typescript +closedLoc = await closedLoc.removeSecret("Encrypted keypair"); +``` + +## Request a Secret recovery + +```typescript +const recoveryRequestId = await client.secretRecovery.createSecretRecoveryRequest({ + requesterIdentityLocId: UUID.fromDecimalStringOrThrow("204903158696868944108230121110623799462"), + secretName: "Encrypted keypair", + challenge: "fde91ce2-bd96-4491-86b4-4dbef32b8c4e", + 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" + }, +}); +``` + +## Download recovered Secret's value + +In order to download the secret's value, the request must first have been approved by the LLO. + +```typescript +const secretValue = await client.secretRecovery.downloadSecret({ + requesterIdentityLocId: UUID.fromDecimalStringOrThrow("204903158696868944108230121110623799462"), + challenge: "fde91ce2-bd96-4491-86b4-4dbef32b8c4e", + requestId: "71d61cca-bed4-4801-a6e0-f976b0add839", +}); +``` + +:::note +The `challenge` and `requesterIdentityLocId` must match the values provided when request the recovery. +The value of `requestId` is the one returned by `createSecretRecoveryRequest(...)`. +:::