In the previous post, we covered the basics of how to store the data in the Solana blockchain. You can also find remote jobs related to blockchain to make money out of your passion. In this post, we will look into storing details in the blockchain based on the current state of the application.
Application States
Eventually, the code has 10 states/events. In each state, we add or update data to the account. I will go through them briefly. I will post some snippets of code here. The full code of the application is available here.
User/Driver Profile
After a user connects the wallet, the applications give a form to store the name and phone number. I want it to be extensible so I am storing data in Arweave. Arweave is a decentralized storage blockchain. Initially, I was not sure if I would go with the ride-sharing app option or an NFT app, so using Arweave made a lot of sense. In an NFT project, we can store NFT data in Arweave.
Setting up Arweave
I followed the below steps to get Arweave running.
- Install docker
- Get the Testweave docker image from here
- Get the image up, using docker-compose up.
- In dapp code, a simple class is setup to get arweave service reference. Below is the snippet of the class. Full source code is available in github repo.
[sourcecode language="javascript"] class ArweaveService{ arweave:Arweave; testWeave?:TestWeave; walletKey?:JWKInterface; constructor(){ this.arweave=Arweave.init({ host:"localhost", port:1984, protocol:"http", }); TestWeave.init(this.arweave).then((testWeave)=>{ this.testWeave=testWeave; this.walletKey=this.testWeave.rootJWK; }); } } [/sourcecode]
Few of the issues which I encountered while setting up Arweave
- If you are using WSL in windows, stop the net service with this command. net stop service else there is a conflict while running the Arweave server
- In docker-compose.yml, Postgres ports:- “5432:5432”, I changed it to “5432” else there was a port conflict.
Saving data in Arweave
Below is the source code to save data in Arweave. It is similar to updating data in Solana.
[sourcecode language="javascript"] private async saveData(jsonData : string):Promise<string>{ let transaction = await this.arweave.createTransaction({ data:jsonData },this.walletKey!); //Buffer.from(jsonData,'utf-8') transaction.addTag("Content-Type","text/plain"); //sign await this.arweave.transactions.sign(transaction,this.walletKey!); //post await this.arweave.transactions.post(transaction); await this.testWeave!.mine(); const hash=transaction.id; const status=await this.arweave.transactions.getStatus(hash); return hash; } [/sourcecode]
In the above code, we are signing and then posting the transaction to Arweave. Then we wait for the transaction to mine and get the hash. We store this hash in Solana Account.
Storing Arweave Hash in Solana
The above code gives us the hash of the account JSON data stored in Arweave. Now we store this hash in Solana. In the previous post, I have mentioned how to create an instruction, transaction, and publishing it on the blockchain. Now we sell all of it together to store Arweave data. We will use a similar code for all the different states which we need to store to get the application running.
Disclaimer:- The below code is not best optimized or follows the best practice. It just shows how we can store data in the blockchain using a string array. There is a lot of scope for improvement.
[sourcecode language="javascript"] const instr: SingleInstruction = new SingleInstruction(); let instr_type="1"; if (accountType===AccountTypes.Driver) instr_type="3"; instr.full_instruction=replaceAt(instr.full_instruction,0,instr_type); instr.full_instruction=replaceAt(instr.full_instruction,2,hash); async saveSingleAccountDataInstruction( connection:Connection, pubKeyString: string, instrRecord: SingleInstruction, wallet:WalletAdapter ):Promise<RpcResponseAndContext<SignatureResult>>{ const profileKey=new PublicKey(pubKeyString); const profileInstruction = new TransactionInstruction({ keys:[{pubkey:profileKey,isSigner:false,isWritable:true}], programId, data:Buffer.from(borsh.serialize(SingleInstructionSchema,instrRecord)), }); const trans=await setPayerAndBlockhashTransaction( wallet, profileInstruction ); const signature = await signAndSendTransaction(wallet,trans); const result= await connection.confirmTransaction(signature,"singleGossip"); console.log(result); return result; } [/sourcecode]
Application States To Save
The above code is saving the transaction to the blockchain. Now we will go through the different events which happen in the application. For each event, we will store the state in the blockchain. Each will have a separate instruction type so that the rust program can parse the data in the instruction accordingly.
- Lets start with step 1, when the user stores profile data.
[sourcecode language="javascript"] //At 0 position we store the instruction type. 1 is store customer data and 3 to store driver data instr.full_instruction=replaceAt(instr.full_instruction,0,instr_type); //From position 2 to 46 we store Arweave transaction hash instr.full_instruction=replaceAt(instr.full_instruction,2,hash); [/sourcecode]
- Then in step 2, the customer stores ride data i.e. source and destination latitude/longitudes and distance in km(s) from source to destination.
[sourcecode language="javascript"] //Instruction type 2 instr.full_instruction=replaceAt(instr.full_instruction,0,"2"); //store ride details for customer //at position 45 we store source localtion latitude instr.full_instruction=replaceAt(instr.full_instruction,45,pfromLat); //at position 53 we store source clocation longitude instr.full_instruction=replaceAt(instr.full_instruction,53,ptoLat); //at position 61 we store destination location latitude instr.full_instruction=replaceAt(instr.full_instruction,61,pfromLong); //at position 69 we store destination location longitude instr.full_instruction=replaceAt(instr.full_instruction,69,ptoLong); //at position 77 we store distance in km(s) from source to destination instr.full_instruction=replaceAt(instr.full_instruction,77,pdistance); [/sourcecode]
- Following instruction is for step 4 when the driver selects the customers and this information gets updated in the customer’s account. So the customer can see matched drivers. The current code has a limit of upto 3 customers per driver. Code is calculating min and max distance for selected 3 customers if the driver starts the ride on confirmation of at least 2 customers. So we are storing possible min and max distance value to the customer, so the customer will have a cost range if the driver starts the ride before confirmation from the third customer.
[sourcecode language="javascript"] //loop through the customers selected by driver for the current ride. instr.full_instruction=replaceAt(instr.full_instruction,0,"4"); //for min option, minimum distance is stored for the selected customers instr.full_instruction = replaceAt(instr.full_instruction,77,minmax[0][1]); instr.full_instruction = replaceAt(instr.full_instruction,186,minmax[0][0]); //for max option, maximum distance is stored for the selected customers instr.full_instruction = replaceAt(instr.full_instruction,190,minmax[1][1]); instr.full_instruction = replaceAt(instr.full_instruction,194,minmax[1][0]); //driver address instr.full_instruction = replaceAt(instr.full_instruction,98,walletAddress!.toBase58()); //customer address instr.full_instruction = replaceAt(instr.full_instruction,142,d.customers[i]); [/sourcecode]
- The below instruction is for step 5 when the customer selects the driver. The instruction updates both customer and driver account
[sourcecode language="javascript"] //instruction type 5 instr.full_instruction=replaceAt(instr.full_instruction,0,"5"); //driver //from position 98 to 141 we store base58 driver address string in customer account instr.full_instruction = replaceAt(instr.full_instruction,98,driverAddress); //from position 142 to 185 we store base58 customer address string in driver account instr.full_instruction = replaceAt(instr.full_instruction,142,walletAddress!.toBase58()); [/sourcecode]
- Then we go to step 6. As customers start confirming the ride, the driver waits for confirmations from 2 customers. Then the driver updates the customer that he is starting and the customer’s ride status updates to start.
[sourcecode language="javascript"] //step 6 instr.full_instruction=replaceAt(instr.full_instruction,0,"6"); //confirmed ride string. It store 0 or 1 based on if driver confirmed the ride for customer. instr.full_instruction = replaceAt(instr.full_instruction,190,confirmedRides[i]); //this is the customer number i.e 1,2 or 3 serial no. of customers selected by driver. Currently this is being sent as we are updating customer data to show driver is not available to customer as he has already started the ride in case if confirmed ride bit is 0 instr.full_instruction = replaceAt(instr.full_instruction,194,""+(i+1)); //driver account address instr.full_instruction = replaceAt(instr.full_instruction,98,walletAddress!.toBase58()); //customer account address instr.full_instruction = replaceAt(instr.full_instruction,142,d.customers[i]); //distance of customer rides added instr.full_instruction = replaceAt(instr.full_instruction,186,d.ridesDistance); //driver distance covered. This and above field are used to calculate the ride cost instr.full_instruction = replaceAt(instr.full_instruction,77,d.distance); //public key to pay later. Storing public key, so payment can be done later. instr.full_instruction = replaceAt(instr.full_instruction,198,wallet!.publicKey!.toBase58()); [/sourcecode]
- The below instruction is for step 7. It just updates drivers’ account data with flat (0 or 1) for confirmed rides for selected customers.
[sourcecode language="javascript"] //step 7 instr.full_instruction=replaceAt(instr.full_instruction,0,"7"); //confirmedRides is 4 bit string. each bit stores confirmed ride flag for customer selected instr.full_instruction = replaceAt(instr.full_instruction,190,d.confirmedRides); [/sourcecode]
- Few steps are left now. In step 8, Driver marks the ride as finished.
[sourcecode language="javascript"] //step 8 instr.full_instruction=replaceAt(instr.full_instruction,0,"8"); //ridesToFinish is string of bits to store ride completion status. All bits become 0 when all rides are finished instr.full_instruction=replaceAt(instr.full_instruction,190,d.ridesToFinish); [/sourcecode]
- The below instruction is for step 9. The customer pays the due amount.
[sourcecode language="javascript"] //step 9 //get driver address const driverAddress:any = getSelectedDriver(customerAccountData); //get the amount of token const val:number=d.cost*1*1000000000; //convert to lamports //get driver public address to transfer the token const pubKeyDriver:string=customerAccountData?.driver_pub_key; const pubKeys=[walletAddress!.toBase58(),driverAddress]; const instr:SingleInstruction = new SingleInstruction(); instr.full_instruction=replaceAt(instr.full_instruction,0,"9"); instr.full_instruction = replaceAt(instr.full_instruction,142,walletAddress!.toBase58()); await profileService.sendMoneyAndUpdateDriver(connection!,wallet,pubKeyDriver,val,pubKeys,instr); [/sourcecode]
Note:- In the source code I am adding a system instruction in js code to transfer the tokens. It would be better if this code is in rust as it can be edited.
[sourcecode language="javascript"] const instruction = SystemProgram.transfer({ fromPubkey: wallet!.publicKey!, toPubkey: destPubkey, lamports, }); [/sourcecode]
- There is one more step to clear the data stored in accounts
Solana UI Callback
One more useful javascript function in Solana web3 is connetion.onAccountChange. This function has two parameters, one for the Solana account and the other is a callback function. Whenever there is a change in data for the associated Solana account, this callback is triggered.
Summary
Once you get the hang of understanding how data is stored in the blockchain and how applications can interact with it, developing applications becomes easier. There is a change in data or state, we store it in the blockchain.
1 thought on “Solana Ride Sharing Application – Part 2”