If the option is selected, files are zipped and sent to Arweave using Bundlr - which is a PoS network built on top of Arweave. Bundlr nodes bundle multiple transactions, each time for two minutes, and submit them onto Arweave, which makes the process way faster and cheaper, whilst remaining as reliable (Learn more).
Since the project is deployed on testnets (Polygon Mumbai), the archive is sent to a devnet bundler, which allows the user to pay with testnet currencies.
The only difference with mainnet is that the files are not actually moved to Arweave, but instead deleted after a week.
How are the files sent with Bundlr?
The process is different from the one used to send files to IPFS, and requires more input from the user. It can be condensed into the following steps:
Connect the user wallet to the Bundlr node.
Prepare the upload
Create the zip archive
Find the price in MATIC for n amount of data.
Fund the Bundlr "wallet" - the address associated to the user's private key
Create the transaction and upload the data.
Each stage is outlined below, with details in the relevant code.
Connecting to Bundlr
uploadToArweave.js
constinitializeBundlr=async (provider, chainId) => {// Find the RPC url based on the chain (mainnet or testnet)constrpcUrl= chainId ===80001?process.env.NEXT_PUBLIC_MUMBAI_RPC_URL:process.env.NEXT_PUBLIC_POLYGON_RPC_URL;// Get the appropriate bundler url (mainnet or devnet)constbundlrUrl= networkMapping[chainId].Bundlr[0];// Connect with MATIC as a currency to the appropriate nodeconstbundlr=newWebBundlr(bundlrUrl,'matic', provider, { providerUrl: rpcUrl, });awaitbundlr.ready().catch((err) => {console.log(err);toast.error('Please connect to the Arweave network to continue'); });let isReady;if (bundlr.address ==='Please run `await bundlr.ready()`') { isReady =false; } else { isReady =true; }return { instance: bundlr, isReady };};
After the user is connected, they can start interacting with the network.
Preparing the transaction
uploadToArweave.js
constuploadToArweave= async javasconst uploadToArweave=async ( bundlr, userBalance, files,// ...) => {try {// Get the instance created during 'initializeBundlr'constbundlrInstance=bundlr.instance;// Get each file dataconstfilesObj=files.map((file) =>file.originFileObj);// Prepare a read streamconstpreparedFiles=awaitprepareReadStream(filesObj);// Create the zip archiveconstzipFile=awaitcreateZip(preparedFiles, formattedPromiseName);// Prepare a read stream for the zip archiveconstpreparedZip=awaitprepareReadStream( [zipFile], formattedPromiseName, );// Get the price for the upload based on the size of the archiveconstrequiredPrice=awaitbundlrInstance.getPrice(zipFile.size);// Find the balance of the user on BundlrconstbundlrStartingBalance=awaitbundlrInstance.getLoadedBalance();// Find the needed amount for this uploadconstrequiredFund= requiredPrice.minus(bundlrStartingBalance).multipliedBy(1.1).integerValue();// Fund the instance if neededconstfundTx=awaitfundBundlr( bundlrInstance, bundlrStartingBalance, userBalance, requiredPrice, requiredFund,// ... );... }}
uploadToArweave.js
// Funding the user walletconstfundBundlr=async ( bundlrInstance, bundlrStartingBalance, userBalance, requiredPrice, requiredFund,// ...) => {// Get the balance of the instance and the userconstformattedRequiredPrice=bundlrInstance.utils.unitConverter(requiredPrice).toString();// If the balance is not enough for the file(s)if (bundlrStartingBalance.isLessThan(requiredPrice)) {// Format the price// 'formattedRequiredFund' is actually 1.1x the required price,// just to be sure it will be enough// The remaining will still be available on the user walletconstformattedRequiredFund=bundlrInstance.utils.unitConverter(requiredFund).toFixed(4).toString();// Fund the instanceconstfundTx=await toast.promise(bundlrInstance.fund(requiredFund), { pending:`Funding your Bundlr wallet with ${formattedRequiredFund} MATIC...`, success:'Funded Bundlr successfully!', error:'Failed to fund Bundlr', }).catch((err) => {console.log(err);returnfalse; });return fundTx; }returntrue;};
Sending the transaction
uploadToArweave.js
constuploadToArweave= async javasconst uploadToArweave=async ( bundlr, userBalance, files,// ...) => {try {...// Upload the zip to BundlrconstuploadedFiles=awaituploadFilesToBundlr( bundlrInstance, preparedZip, setStatusMessage, );return uploadedFiles[0]; } ...}
uploadToArweave.js
constuploadFilesToBundlr=async ( bundlrInstance, preparedFiles,) => {// Upload each file and get the url// Here we only have a zip archive, but having it like this can be// equally adequate for both caseslet uploadedFiles = [];for (constfileof preparedFiles) {let uploadProgress =0;// Prepare the uploader// Get a chunked uploaderconstuploader=bundlrInstance.uploader.chunkedUploader;uploader.setBatchSize(1);constuploadOptions= {// Grab the file type we attached to its object tags: [{ name:'Content-Type', value:file.type }], };// Listen for the upload progressuploader.on('chunkUpload', (chunk) => {// Get it into a percentage uploadProgress = ((chunk.totalUploaded /file.size) *100).toFixed(); });// Upload the fileconstuploadTx=await toast.promise(uploader.uploadData(file.stream, uploadOptions), { pending:`Bundlr: uploading ${file.name}... (${uploadProgress}%)`, success:`${file.name} uploaded successfully!`, error:`Failed to upload ${file.name}`, }).catch((err) => {console.log(err);returnfalse; });// Get the urlconstfileId=uploadTx.data.id;uploadedFiles.push(fileId); }// Return all the linksreturn uploadedFiles;};