When nulink supports multiple networks, users can set their preferred network using the following method:
import {setCurrentNetworkKey} from"@nulink_network/nulink-sdk";//The currently available network: NETWORK_LIST.HorusawaitsetCurrentNetworkKey(NETWORK_LIST.Horus)
Set Data Storage
Since the purpose of nulink pre is to encrypt and share data (files), it inevitably involves uploading (storing) and downloading (retrieving) files. Therefore, it is necessary to set up the methods for accessing files. The process is as follows:
Then, you need to implement the setData and getData methods of the DataCallback structure. Since we need to upload data (files) in batches in our use case, while usually retrieving them one by one, we need to implement the setData method for batch uploading and the getData method for retrieving data individually.
constdataCallback:DataCallback= { setData: setIPFSData, getData: getIPFSData }//Set the external storage used by the Pre process to IPFS (for example, encrypted files uploaded by users will be stored in this storage, and users can customize the storage).StorageManager.setDataCallback(dataCallback)
After setting up the callback functions, when we upload files using the uploadDataByCreatePolicy method in pre, the callback function setData you have set will be automatically invoked. Later, when we retrieve files using getDataContentAsUser, the getData function will be automatically called.
Please note that the Account parameter is optional. If the setData callback function defined by the user requires account information (such as signing data with the account's private key), the callback function can be defined with an additional parameter for the account. In the pre process, when the setData callback function is called, the account information will be passed to the user-defined callback function, allowing the callback function to access the current account information (account parameter).
More details:
For more information, you can refer to this example: upload.backend.ts
If the account parameter is not needed, you can refer to this example: ipfs.ts
With these preparations done, we can now proceed to the code for the pre process
Here's the story: We have two characters in our story. One is named Alice, who uploads the data. The other is Bob, who wants to use the data uploaded by Alice. So, Bob requests Alice's uploaded data in our network. Once Alice sees Bob's request in our system, she has the option to either reject the request, in which case Bob cannot access the data, or approve Bob's request. If Alice approves the request, she can send an on-chain approval transaction, and upon successful approval, Bob can download the file to view or use it.
Create Wallet for Alice
First, In order to use the pre process, we need to first create an account.
//First, we import all the required libraries.import { isBlank, restoreWalletDataByMnemonic, getPolicyGasFee,type DataInfo, DataCategory, uploadDataByCreatePolicy, getUploadedData, createAccountIfNotExist, getOtherShareData, getDataDetails, applyForDataUsagePermission, getDataPendingApprovalAsPublisher, refusalApplicationForUseData, getPolicyTokenCost, approvalApplicationForUseData, getApprovedDataAsPublisher, getApprovedDataAsUser, getDataContentByDataIdAsUser, getPublishedPoliciesInfo, uploadDataBySelectPolicy, getDataByStatus, getMnemonic, getDefaultAccountPrivateKey, logoutWallet, GasInfo} from"@nulink_network/nulink-sdk";import assert from"assert-ts";import { BigNumber, ethers } from"ethers";import { nanoid } from"nanoid";import Web3 from"web3";// Declaring and intializing the mnemonic and password variables.constpassword:string="1";//first We create Alice's wallet and account by passwordconstnuLinkHDWallet1:NuLinkHDWallet=awaitcreateWallet(password);assert(nuLinkHDWallet1);// after we created the wallet, we can loadWallet by passwordconstnuLinkHDWallet2:NuLinkHDWallet|null=awaitloadWallet(password);assert(nuLinkHDWallet2);constnuLinkHDWallet= nuLinkHDWallet2 asNuLinkHDWallet;assert(nuLinkHDWallet1 === nuLinkHDWallet);//also, We can verify whether the user's password is correctconstcorrect:boolean=awaitverifyPassword(password);assert(correct);// We can also determine if the user has created an account locallyconsthasAnAccountInLocal:boolean=awaitexistDefaultAccount();assert(hasAnAccountInLocal);// we can get the account by user password that we have createdconstaccountAlice:Account= (awaitgetWalletDefaultAccount(password)) asAccount;assert(accountAlice);
Alice upload data/files for encrypted
Next, we will upload data using the account we just created, which we'll refer to as "Alice."
// account Alice: as the publisher of the file (file uploader).// Note: We only support one account currently.//Now we can encrypt and upload a file for others to apply for download//1. read a fileconstplainText='file-content....';constenc=newTextEncoder(); // always utf-8consthistoryContent:Uint8Array=enc.encode(plainText);//1.Alice upload fileconstdataList:DataInfo[] = [ {//label: A unique identifier for the file (similar to the file name) that is displayed to the user who needs to apply for the file label:`history-${nanoid()}.pdf`, dataArrayBuffer:historyContent.buffer, },];//2. Alice encrypt and update a file to the ipfs networkawaituploadDataByCreatePolicy(accountAlice,DataCategory.History, dataList);//3. We can get the file just uploadedconstresultList= (awaitgetUploadedData(accountAlice,undefined,1,1000)) asobject;console.log("resultList: ", resultList);console.log('resultList["total"]>0 ', resultList["total"] >0);assert(resultList && resultList["total"] >0);let fileIndex =-1;for (let index =0; index < resultList["list"].length; index++) {constelement= resultList["list"][index];if (element["file_name"] === dataList[0]["name"]) { fileIndex = index;break; }}assert(fileIndex >=0);constuploadDataInfo= resultList["list"][fileIndex];assert(uploadDataInfo["owner_id"] ===accountAlice.id);
Bob requests to use the data/files uploaded by Alice
Then: Bob, far away on the other side of the ocean, wants to use Alice's uploaded data.
1.create wallet for Bob
// Bob, far away on the other side of the ocean, wants to use Alice's uploaded data// account Bob: as the user of the data/file ( data/file requester)//don't forget import libirary ....//Bob find the data/file on Internetconstpassword:string="1";//Create Wallet for Bob//first We create Bob's wallet and account by passwordconstnuLinkHDWallet:NuLinkHDWallet=awaitcreateWallet(password);assert(nuLinkHDWallet);// we can get the account by user password that we have createdconst_accountBob:Account= (awaitgetWalletDefaultAccount(password)) asAccount;assert(_accountBob);
2.Bob views the details of the data that Alice just uploaded by checking the online information of other users' uploaded data.
//Bob finds the file Bob has just uploadedconstdataDataResultList= (awaitgetOtherShareData(_accountBob,undefined,false,undefined,undefined,undefined,1,1000)) asobject;assert(dataDataResultList && dataDataResultList["total"] >0);let dataIndex2 =-1;for (let index =0; index < dataDataResultList["list"].length; index++) {constelement= dataDataResultList["list"][index];if (element["file_name"] === dataList[0]["name"]) { dataIndex2 = index;break; }}assert(dataIndex2 >=0);constfindDataInfo= dataDataResultList["list"][dataIndex2];assert(findDataInfo["owner_id"] ===accountAlice.id);constapplyDataId= findDataInfo["file_id"];//get data detailsconstdataDetails= (awaitgetDataDetails(applyDataId,_accountBob.id)) asobject;//assert(dataDetails["creator_id"] === accountAlice.id);assert(dataDetails["file_id"] === applyDataId);assert(parseInt(dataDetails["status"]) ===0); //Is not to apply for
3.Bob is particularly interested in the data uploaded by Alice. So, Bob requests to use the data that Alice has just uploaded.
//Bob requests permission to use the data for 7 daystry {awaitapplyForDataUsagePermission(applyDataId, _accountBob,7);} catch (e) {console.log("bob apply data failed", e);assert(false);}
4.Alice reviews the usage requests from others for the data she uploaded.
At first, Alice rejected Bob's request to use the data.
//Alice receives Bob's data usage request// Alice reviews the usage requests from others for the data she uploaded.constdataNeedToApprovedResultList=awaitgetDataPendingApprovalAsPublisher(accountAlice,1,1000);let fileIndex3 =-1;for (let index =0; index < dataNeedToApprovedResultList["list"].length; index++) {constelement= dataNeedToApprovedResultList["list"][index];if (element["file_id"] === applyDataId) { fileIndex3 = index;break; }}assert(fileIndex3 >=0);assert(dataNeedToApprovedResultList && dataNeedToApprovedResultList["total"] >0);constneedToApprovedDataInfo= dataNeedToApprovedResultList["list"][fileIndex3];assert(needToApprovedDataInfo["file_owner_id"] ===accountAlice.id);//Alice rejected the file usage requestawaitrefusalApplicationForUseData(accountAlice, needToApprovedDataInfo["apply_id"]);
5.Bob finds this file to be very useful, so he makes a second request.
//Bob apply file for usage again. The application period is three days, less than the previous seven daystry {awaitapplyForDataUsagePermission(applyDataId, _accountBob,3);} catch (e) {console.log("bob reapply file failed", e);assert(false);}
Alice approves Bob's request to use the file
1.Alice is moved by Bob's persistence and after much consideration, she finally agrees to Bob's usage request.Since we need to send approval transactions to the current blockchain network, we first need to assess the gas fees as well as the service fees for requesting others' usage.
//Alice receives Bob's file usage request againconstdataNeedToApprovedResultList2=awaitgetDataPendingApprovalAsPublisher(accountAlice,1,1000);assert( dataNeedToApprovedResultList2 && dataNeedToApprovedResultList2["total"] >0);let dataIndex4 =-1;for (let index =0; index < dataNeedToApprovedResultList2["list"].length; index++) {constelement= dataNeedToApprovedResultList2["list"][index];if (element["file_id"] === applyDataId) { dataIndex4 = index;break; }}assert(dataIndex4 >=0);constneedToApprovedDataInfo2= dataNeedToApprovedResultList2["list"][dataIndex4];assert(needToApprovedDataInfo2["file_owner_id"] ===accountAlice.id);//At this point Alice approves Bob's file usage request, Due to on-chain approval of Bob's request, we first evaluate gas and service fees//1. Alice calc server fee (wei): // For the main chain TBSC/BSC, the token is TNLK/NLK. // For the side chains, they use the native currency of the respective chain (e.g., Mumbai uses TMATIC/MATIC, OKX X1 Chain uses TOKB/OKB).conststartDate:Date=newDate();conststartMs:number=Date.parse(startDate.toString());constendMs:number= startMs + (needToApprovedDataInfo2["days"] asnumber) *24*60*60*1000;constendDate:Date=newDate(endMs); // start_at is seconds, but Date needs millisecondsconstserverFeeNLKInWei:BigNumber=awaitgetPolicyTokenCost(accountAlice, startDate, endDate,2);constserverValue=Web3.utils.fromWei(serverFeeNLKInWei.toString(),"ether");console.log("server nlk fee ether is:", serverValue);//2. Alice calc gas fee (wei): the chain of bsc test tokenconstgasInfo:GasInfo=awaitgetPolicyGasFee(_accountBob.id, needToApprovedDataInfo2["apply_id"],2,1, startMs/1000, endMs/1000,BigNumber.from(serverFeeNLKInWei));//First, let's record the list of requests from other individuals that Alice has already approved, for future reference.constaliceApprovedfilesListLast=awaitgetApprovedDataAsPublisher( accountAlice,1,1000 );//Note: Please make sure that the account has sufficient tnlk and bsc testnet tokens before this, otherwise the approval will fail//Alice approves Bob's application for file usage. Whenever Alice approves a file request, an on-chain policy is createdawaitapprovalApplicationForUseData( accountAlice,_accountBob.id, needToApprovedDataInfo2["apply_id"],2,1, startDate, endDate,"",//remark"",//porterUrigasInfo.gasFee);
2.As it is an on-chain transaction, there might be some delay. At this point, we need to wait for the on-chain transaction to be successfully confirmed by web3 before Alice can view her latest list of approved documents.
let aliceApprovedDataList:any=null;do {awaitsleep(10000); //10 seconds//Alice, as the publisher of the file, obtains the list of files that she has successfully approved aliceApprovedDataList =awaitgetApprovedDataAsPublisher( accountAlice,1,1000 );/*return data format: { list: [ { apply_id, file_id:, proposer, proposer_id, file_owner:, file_owner_id:, policy_id, hrac, start_at:, end_at, created_at } ... ], total: 300, } */ } while (!aliceApprovedDataList || aliceApprovedDataList["total"] <= aliceApprovedfilesListLast["total"] );assert(aliceApprovedDataList && aliceApprovedDataList["total"] >0);
Bob checks his list of requested files, and he can use the data now
1.At this point, Bob checks his list of requested files and discovers that Alice has approved his request to use the file. So, Bob obtains the details of the document for future reference and downloading.
//Record this policy ID and account Bob for future use.let policyId;let accountBob;constdataIndex2s:number[] = [] //Array(numReqData).fill(-1);for (let index =0; index < aliceApprovedDataList['list'].length; index++) {constelement= aliceApprovedDataList['list'][index]if (element['file_id'] === applyDataId) {dataIndex2s.push(index)assert(element['file_owner_id'] ===accountAlice.id)const_policyId= element['policy_id'] policyId = _policyId;//console.log(`index_${index} data/file policy Id: ${_policyId}`)constaccountBobId= element['proposer_id']const_accountBob= bobAccountId2AccountMap[accountBobId] accountBob = _accountBob;//Bob finds out that his application has been approved by Alice. Bob now has permission to view the contents of the fileconstbobBeApprovedDataList=awaitpre.getApprovedDataAsUser(_accountBob,1,1000)/*return data format: { list: [ { apply_id, file_id:, proposer, proposer_id, file_owner:, file_owner_id:, policy_id, hrac, start_at:, end_at, created_at } ... ], total: 300, } */assert(bobBeApprovedDataList && bobBeApprovedDataList['total'] >0)let dataIndex6 =-1for (let index =0; index < bobBeApprovedDataList['list'].length; index++) {constelement= bobBeApprovedDataList['list'][index]if (element['file_id'] === applyDataId) { dataIndex6 = indexbreak } }assert(dataIndex6 >=0)constbobBeApprovedDataInfo= bobBeApprovedDataList['list'][dataIndex6]assert(bobBeApprovedDataInfo['file_owner_id'] ===accountAlice.id)constpolicyId2= bobBeApprovedDataInfo['policy_id']assert(policyId2 === _policyId)
2.At this point, Bob downloads and views the data.
//Finally, Bob gets the contents of the data/fileconstarrayBuffer:ArrayBuffer=awaitpre.getDataContentByDataIdAsUser( _accountBob, bobBeApprovedDataInfo['file_id'] )constdataContent:string=Buffer.from(arrayBuffer).toString()console.log('dataContent: ', dataContent)console.log('plainText: ', plainText)assert(dataContent === plainText)//finish } }assert(dataIndex2s.length>=0&&dataIndex2s.length>0)//finish
Alice obtain the on-chain policy information published by herself
Also, Whenever Alice approves a file request, an on-chain policy is created. Alice can also obtain the on-chain policy information published by herself
// Whenever Alice approves a file request, an on-chain policy is created// Alice can also obtain the on-chain policy information published by herselfconstdataPolicys=awaitgetPublishedPoliciesInfo(accountAlice,1,1000);assert(!isBlank(dataPolicys));
Alice upload file by select Published Policy
1.Alice also can encrypt and update a file to the ipfs network by select an existing on-chain policy
//Alice also can encrypt and update a file to the ipfs network by select an existing on-chain policyconstplainText2="This is a philosophy book content";consthistoryContent2:Uint8Array=enc.encode(plainText2);//1.upload fileconstdataList2:DataInfo[] = [ { label:`philosophy-${nanoid()}.pdf`, dataArrayBuffer:historyContent2.buffer, },];//Files/Data uploaded by using published policies do not need approval. Bob can use the files directly, so there is no approval recordconstfileIds=awaituploadDataBySelectPolicy( accountAlice,DataCategory.Philosophy, dataList2, policyId);
Bob get data no need approve by Alice's published policys
2.Bob can directly download Alice's associated policy upload file without waiting for Alice's approval, because the associated policy has already been created and does not need repeated approval.
//Bob can directly download Alice's associated policy upload file without waiting for Alice's approval,//because the associated policy has already been created and does not need repeated approval. Note: This publish policy value is available for Bob//Bob get new upload file contentconstarrayBuffer2:ArrayBuffer=awaitgetDataContentByDataIdAsUser( _accountBob, fileIds[0]);constdataContent2:string=Buffer.from(arrayBuffer2).toString();console.log("dataContent2: ", dataContent2);console.log("plainText2: ", plainText2);assert(dataContent2 === plainText2);//you can get all status files for mine apply: The files I applied for//status 0: all status, include: applying, approved, rejectedconstdata= (awaitgetDataByStatus(undefined,_accountBob.id,undefined,undefined,0,1,1000)) asobject;assert(data &&!isBlank(data) && data["total"] >0);
Restore wallet by mnemonic
For wallet more info, you can also restore wallet by mnemonic
// we also can restore wallet by mnemonic//get mnemonic from current walletconstmnemonic:string= (awaitgetMnemonic(password)) asstring;//You can also use the mnemonic word exported by metamask to restore the nulink wallet accountconstnewpassword="111";// restore an wallet to Browser localstorage/indexdb by mnemonic, and we can set an new password when we restore an walletconstnuLinkHDWalletRestore:NuLinkHDWallet=awaitrestoreWalletDataByMnemonic(newpassword, mnemonic);assert(nuLinkHDWallet != nuLinkHDWalletRestore);//You can also export the private key of the nulink wallet account through the user password to import it into the metamask walletlet privatekeyString =awaitgetDefaultAccountPrivateKey(newpassword);assert(privatekeyString !=null);privatekeyString = privatekeyString asstring;//When you are done using it, you can clear the browser's wallet cache data, and use the mnemonic to re-import it the next time you use itawaitlogoutWallet();