diff --git a/README.md b/README.md index 3efd189..6595ee9 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This Turborepo includes the following example applications: | --- | --- | --- | | `@shelby-protocol/cross-chain-accounts` | Shelby cross chain accounts example | [`apps/cross-chain-accounts`](./apps/cross-chain-accounts) | | `shelby-download-example` | An example app to demonstrate downloading blobs using the Shelby SDK | [`apps/download-blob`](./apps/download-blob) | +| `@shelby-protocol/upload-example` | An example app to demonstrate uploading blobs using the Shelby SDK | [`apps/upload-blob`](./apps/upload-blob) | diff --git a/apps/upload-blob/.env.example b/apps/upload-blob/.env.example new file mode 100644 index 0000000..453ad6e --- /dev/null +++ b/apps/upload-blob/.env.example @@ -0,0 +1,2 @@ +SHELBY_ACCOUNT_PRIVATE_KEY=ed25519-priv-0xYourPrivateKeyHere +SHELBY_API_KEY=AG-YourAPIKeyHere diff --git a/apps/upload-blob/README.md b/apps/upload-blob/README.md new file mode 100644 index 0000000..4aa40a5 --- /dev/null +++ b/apps/upload-blob/README.md @@ -0,0 +1,126 @@ +# Shelby Upload Blob Example + +An example application demonstrating how to upload blobs using the Shelby SDK. This app uploads a specified file to a Shelby account and stores it on the Shelby network. + +## Prerequisites + +- Node.js >= 22 +- pnpm package manager +- A Shelby account with sufficient balance for blob storage +- Shelby API key +- Shelby account private key + +## Installation + +1. Clone the repository and navigate to the upload-blob directory: + ```bash + cd apps/upload-blob + ``` + +2. Install dependencies: + ```bash + pnpm install + ``` + +## Environment Variables + +Create a `.env` file in the root of this project directory with the following required environment variables. You can copy the `.env.example` file as a starting point: + +```bash +cp .env.example .env +``` + +Then update the values in your `.env` file: + +```env +SHELBY_ACCOUNT_PRIVATE_KEY=your_private_key_here +SHELBY_API_KEY=your_api_key_here +``` + +More information on obtaining an API key on the [Shelby docs site](https://docs.shelby.xyz/sdks/typescript/acquire-api-keys). + +## Configuration + +The example is currently configured to upload a file named `whitepaper.pdf` from the `assets/` directory. You can modify this by changing the configuration constants in `src/index.ts`: + +```typescript +// The file to upload (relative to cwd). +const UPLOAD_FILE = join(process.cwd(), "assets", "whitepaper.pdf"); +// How long before the upload expires (in microseconds from now). +const TIME_TO_LIVE = 60 * 60 * 1_000_000; +// The blob name to use in Shelby (can be different from the local file name). +const BLOB_NAME = "whitepaper.pdf"; +``` + +## Usage + +Run the example using the `upload` script: + +```bash +pnpm upload +``` + +This will execute the TypeScript file directly using tsx with the environment variables from your `.env` file. + +### Alternative Execution + +You can also run the TypeScript file directly using tsx: + +```bash +npx tsx --env-file=.env src/index.ts +``` + +## How It Works + +1. **Environment Validation**: The app first validates that all required environment variables are set +2. **Client Initialization**: Creates a Shelby client instance connected to the Shelbynet network +3. **Account Setup**: Creates a signer using the private key from the environment variable +4. **File Reading**: Reads the specified file from the local filesystem +5. **Blob Upload**: Uploads the file to the Shelby account with the specified name and expiration time +6. **Success Confirmation**: Prints a success message when the upload completes + +## Output + +When successful, this example will: +- Read the file from the `assets/` directory +- Upload the blob to your Shelby account +- Set an expiration time for the blob (1 hour by default) +- Print progress messages to the console + +## Troubleshooting + +### Common Issues + +1. **SHELBY_ACCOUNT_PRIVATE_KEY is not set in .env** + - Verify your private key is correctly set in the `.env` file + - Ensure there are no extra spaces or quotes around the private key + +2. **SHELBY_API_KEY is not set in .env** + - Verify your API key is correctly set in the `.env` file + - Ensure there are no extra spaces or quotes around the API key + +3. **Blob already exists (EBLOB_WRITE_CHUNKSET_ALREADY_EXISTS)** + - This blob has already been uploaded to your account + - Consider changing the `BLOB_NAME` or deleting the existing blob first + +4. **Insufficient balance for transaction fee (INSUFFICIENT_BALANCE_FOR_TRANSACTION_FEE)** + - Your account doesn't have enough APT to pay for the transaction fee + - Add more APT tokens to your account using [the faucet](https://docs.shelby.xyz/apis/faucet/aptos) + +5. **Insufficient funds for blob storage (EBLOB_WRITE_INSUFFICIENT_FUNDS)** + - Your account doesn't have enough Shelby tokens to pay for blob storage + - Add more Shelby tokens to your account using [the faucet](https://docs.shelby.xyz/apis/faucet/shelbyusd) + +6. **Rate limit exceeded (429)** + - Wait a moment before retrying + - Consider implementing exponential backoff for production use + +7. **Server errors (500)** + - This indicates an issue with the Shelby service + - Contact Shelby support if this occurs repeatedly + +## File Requirements + +- The example file `whitepaper.pdf` is included in the `assets/` directory +- You can replace this file with any file you want to upload +- Make sure to update the `UPLOAD_FILE` and `BLOB_NAME` constants accordingly diff --git a/apps/upload-blob/assets/whitepaper.pdf b/apps/upload-blob/assets/whitepaper.pdf new file mode 100644 index 0000000..f587781 Binary files /dev/null and b/apps/upload-blob/assets/whitepaper.pdf differ diff --git a/apps/upload-blob/package.json b/apps/upload-blob/package.json new file mode 100644 index 0000000..a13d4da --- /dev/null +++ b/apps/upload-blob/package.json @@ -0,0 +1,32 @@ +{ + "name": "@shelby-protocol/upload-example", + "version": "1.0.0", + "description": "An example app to demonstrate uploading blobs using the Shelby SDK", + "type": "module", + "scripts": { + "upload": "tsx --env-file=.env src/index.ts", + "lint": "biome check .", + "fmt": "biome check . --write" + }, + "keywords": [ + "shelby", + "sdk", + "blob", + "storage" + ], + "author": "Akasha ", + "license": "MIT", + "dependencies": { + "@aptos-labs/ts-sdk": "^5.1.1", + "@shelby-protocol/sdk": "^0.0.4" + }, + "devDependencies": { + "@biomejs/biome": "2.2.4", + "tsx": "^4.20.5", + "typescript": "^5.9.2", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=22" + } +} diff --git a/apps/upload-blob/src/index.ts b/apps/upload-blob/src/index.ts new file mode 100644 index 0000000..7fffb20 --- /dev/null +++ b/apps/upload-blob/src/index.ts @@ -0,0 +1,39 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { Account, Ed25519PrivateKey, Network } from "@aptos-labs/ts-sdk"; +import { ShelbyNodeClient } from "@shelby-protocol/sdk/node"; + +// The file to upload (relative to cwd). +const UPLOAD_FILE = join(process.cwd(), "assets", "whitepaper.pdf"); +// How long before the upload expires (in microseconds from now). +const TIME_TO_LIVE = 60 * 60 * 1_000_000; +// The blob name to use in Shelby (can be different from the local file name). +const BLOB_NAME = "whitepaper.pdf"; + +if (!process.env.SHELBY_ACCOUNT_PRIVATE_KEY) { + throw new Error("Missing SHELBY_ACCOUNT_PRIVATE_KEY"); +} +if (!process.env.SHELBY_API_KEY) { + throw new Error("Missing SHELBY_API_KEY"); +} + +// 1) Initialize a Shelby client (auth via API key; target shelbynet). +const client = new ShelbyNodeClient({ + network: Network.SHELBYNET, + apiKey: process.env.SHELBY_API_KEY, +}); + +// 2) Create an Aptos account object from your private key. +const signer = Account.fromPrivateKey({ + privateKey: new Ed25519PrivateKey(process.env.SHELBY_ACCOUNT_PRIVATE_KEY), +}); + +// 3) Upload the blob to Shelby (reads the file fully into memory first). +await client.upload({ + blobData: readFileSync(UPLOAD_FILE), + signer, + blobName: BLOB_NAME, + expirationMicros: Date.now() * 1000 + TIME_TO_LIVE, +}); + +console.log("✓ Uploaded", BLOB_NAME, "successfully."); diff --git a/apps/upload-blob/tsconfig.json b/apps/upload-blob/tsconfig.json new file mode 100644 index 0000000..dac2510 --- /dev/null +++ b/apps/upload-blob/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "allowJs": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": false, + "lib": ["ES2022", "DOM"], + "types": ["node", "vitest/globals"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3537d12..2759064 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,28 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/node@22.18.9)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + apps/upload-blob: + dependencies: + '@aptos-labs/ts-sdk': + specifier: ^5.1.1 + version: 5.1.1(got@11.8.6) + '@shelby-protocol/sdk': + specifier: ^0.0.4 + version: 0.0.4(@aptos-labs/ts-sdk@5.1.1(got@11.8.6)) + devDependencies: + '@biomejs/biome': + specifier: 2.2.4 + version: 2.2.4 + tsx: + specifier: ^4.20.5 + version: 4.20.6 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@22.18.9)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + packages/table-generator: dependencies: tsx: