# FastFS Upload Format Documentation FastFS is a decentralized file storage system built on the NEAR blockchain that allows users to upload and serve files through a standardized protocol. Files are stored on-chain and served through a web interface with predictable URLs. ## Related Repositories - **FastFS Server**: https://github.com/fastnear/fastfs-server - **FastData Indexer**: https://github.com/fastnear/fastdata-indexer - **Drag & Drop Interface**: https://github.com/fastnear/fastdata-drag-and-drop ## Overview FastFS uses NEAR blockchain transactions to store file data. When a file is uploaded, it becomes accessible via a URL that combines the transaction's predecessor ID, receiver ID, and the file's relative path. ### URL Structure Files uploaded to FastFS are accessible via URLs with the following format: ``` https://{predecessor_id}.fastfs.io/{receiver_id}/{relative_path} ``` **Example:** - Transaction: https://nearblocks.io/txns/5vUqtNhYchWyrifviVZ9F6k4QprgfW9fjSAtcGMdQyRw - Resulting URL: https://mob.near.fastfs.io/fastfs.near/fastnear.png - `mob.near` = predecessor_id (uploader) - `fastfs.near` = receiver_id (can be any account) - `fastnear.png` = relative_path (filename) ## Transaction Requirements ### Method Name All FastFS uploads must use the function call method: ``` __fastdata_fastfs ``` ### Data Format Arguments must be serialized using the Borsh format with the following schema structure: #### Borsh Schema ```rust // Rust representation #[derive(Clone, BorshSerialize, BorshDeserialize)] pub enum FastfsData { Simple(Box<SimpleFastfs>), } #[derive(Clone, BorshSerialize, BorshDeserialize)] pub struct SimpleFastfs { pub relative_path: String, pub content: Option<FastfsFileContent>, } #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] pub struct FastfsFileContent { pub mime_type: String, pub content: Vec<u8>, } ``` #### JavaScript Schema ```javascript const FastfsSchema = { FastfsFileContent: { struct: { mimeType: "string", content: { array: { type: "u8" } }, }, }, SimpleFastfs: { struct: { relativePath: "string", content: { option: "FastfsFileContent" }, }, }, FastfsData: { enum: [{ struct: { simple: "SimpleFastfs" } }], }, }; ``` ## Upload Process ### 1. Prepare File Data Create a FastFS data structure: ```javascript const fastfsData = { simple: { relativePath: "path/to/your/file.png", content: { mimeType: "image/png", content: new Uint8Array(fileBuffer) // Your file as bytes } } }; ``` ### 2. Serialize Data Use Borsh serialization to encode the data: ```javascript import { serialize as borshSerialize } from "borsh"; function encodeFfs(ffs) { return near.utils.bytesToBase64(borshSerialize(FastfsSchema.FastfsData, ffs)); } const serializedData = encodeFfs(fastfsData); ``` ### 3. Submit Transaction Send a NEAR transaction with: - **Method:** `__fastdata_fastfs` - **Arguments:** The serialized data from step 2 - **Receiver:** The contract account that will serve as the namespace ## Validation Rules The FastFS system enforces the following validation rules: ### Path Validation - **Maximum relative path length:** 1,024 characters - Path should be a valid relative file path ### Content Validation - **MIME type:** Required and cannot be empty - **Content:** Binary file data as byte array - **Content field:** Optional (can be `null` for file deletion) ### Validation Function ```rust impl SimpleFastfs { pub fn is_valid(&self) -> bool { if self.relative_path.len() > MAX_RELATIVE_PATH_LENGTH { return false; } if let Some(content) = &self.content { if content.mime_type.is_empty() { return false; } } true } } ``` ## Example Implementation Here's a complete example of uploading a file to FastFS: ```javascript import { serialize as borshSerialize } from "borsh"; // Define the schema const FastfsSchema = { FastfsFileContent: { struct: { mimeType: "string", content: { array: { type: "u8" } }, }, }, SimpleFastfs: { struct: { relativePath: "string", content: { option: "FastfsFileContent" }, }, }, FastfsData: { enum: [{ struct: { simple: "SimpleFastfs" } }], }, }; // Prepare the file data const fileData = { simple: { relativePath: "images/logo.png", content: { mimeType: "image/png", content: new Uint8Array(pngFileBuffer) } } }; // Serialize and upload const serializedData = borshSerialize(FastfsSchema.FastfsData, fileData); const base64Data = near.utils.bytesToBase64(serializedData); // Submit transaction await near.functionCall({ contractId: "fastfs.near", methodName: "__fastdata_fastfs", args: base64Data, gas: "300000000000000", // 300 TGas attachedDeposit: "0" }); ``` ## Storage and Indexing The FastFS indexer processes transactions and stores the following data in ScyllaDB: - **Transaction metadata:** Receipt ID, block height, timestamp - **Account information:** Predecessor ID, receiver ID, signer ID - **File data:** Relative path, MIME type, content bytes - **Indexing information:** Shard ID, receipt index, action index ## Access Patterns ### Direct File Access ``` GET https://{predecessor_id}.fastfs.io/{receiver_id}/{relative_path} ``` ### File Deletion Files can be removed by uploading without content by setting the `content` field to `null`: ```javascript const deleteFile = { simple: { relativePath: "path/to/file.json", content: null } }; ``` This will remove the file from the FastFS system. ## Error Handling Common validation failures: - **Path too long:** Relative path exceeds 1,024 characters - **Empty MIME type:** MIME type is required when content is provided - **Invalid Borsh serialization:** Data doesn't match the expected schema - **Transaction failure:** Insufficient gas or other NEAR transaction errors ## Best Practices 1. **File Organization:** Use meaningful directory structures in relative paths 2. **MIME Types:** Always specify accurate MIME types for proper content serving 3. **File Size:** Be mindful of NEAR transaction size limits 4. **Gas Estimation:** Larger files require more gas for transaction processing 5. **Batch Uploads:** For multiple files, consider separate transactions to avoid gas limits ## Integration Examples ### Web Application Upload ```javascript async function uploadToFastFS(file, relativePath) { const fileBuffer = await file.arrayBuffer(); const fastfsData = { simple: { relativePath: relativePath, content: { mimeType: file.type, content: new Uint8Array(fileBuffer) } } }; const serialized = borshSerialize(FastfsSchema.FastfsData, fastfsData); // Submit transaction... } ``` ### Drag and Drop Interface For easy file uploads, you can use the web-based drag and drop interface: - Repository: https://github.com/fastnear/fastdata-drag-and-drop This documentation provides a complete guide for implementing FastFS file uploads. The system provides a decentralized, blockchain-based file storage solution with predictable URL patterns and robust validation.