Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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-protocol/download-example` | An example app to demonstrate downloading blobs using the Shelby SDK | [`apps/download-blob`](./apps/download-blob) |
| `@shelby-protocol/list-example` | An example app to demonstrate listing blobs using the Shelby SDK | [`apps/list-blob`](./apps/list-blob) |
| `@shelby-protocol/query-example` | An example app to demonstrate querying blobs, activities, and network metadata using the Shelby SDK | [`apps/sdk-queries`](./apps/sdk-queries) |
| `@shelby-protocol/upload-example` | An example app to demonstrate uploading blobs using the Shelby SDK | [`apps/upload-blob`](./apps/upload-blob) |

<!-- APPS_TABLE_END -->
Expand Down
5 changes: 5 additions & 0 deletions apps/sdk-queries/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SHELBY_ACCOUNT_ADDRESS=0xYourAccountAddressHere
SHELBY_API_KEY=AG-YourApiKeyHere

# Only required by the delete-blob script, which signs an on-chain transaction.
SHELBY_ACCOUNT_PRIVATE_KEY=ed25519-priv-0xYourPrivateKeyHere
163 changes: 163 additions & 0 deletions apps/sdk-queries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Shelby SDK Query Examples

A collection of small, self-contained scripts demonstrating how to read data from the Shelby protocol using the Shelby SDK. Each script is one file in `src/` and answers a single question — how many blobs does this account own, what is stored under a given name, which storage providers are serving the network, and so on.

Most of these queries hit the Shelby indexer and are read-only, so they need nothing more than an account address and an API key. The one exception is `delete-blob`, which signs an on-chain transaction.

## Prerequisites

- Node.js >= 22
- pnpm package manager
- A Shelby account with uploaded blobs
- Shelby API key

## Installation

1. Clone the repository and navigate to the sdk-queries directory:
```bash
cd apps/sdk-queries
```

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_ADDRESS=your_account_address_here
SHELBY_API_KEY=your_api_key_here

# Only required by the delete-blob script.
SHELBY_ACCOUNT_PRIVATE_KEY=your_private_key_here
```

More information on obtaining an API key on the [Shelby docs site](https://docs.shelby.xyz/sdks/typescript/acquire-api-keys).

## Usage

Each query has its own script. Run whichever one you're interested in:

| Script | File | What it does |
| --- | --- | --- |
| `pnpm query:blobs` | `src/account-blobs.ts` | Lists every blob owned by the account, with full metadata |
| `pnpm query:metadata` | `src/blob-metadata.ts` | Fetches the on-chain metadata for a single named blob |
| `pnpm query:blob-count` | `src/blob-count.ts` | Counts the account's blobs without fetching them |
| `pnpm query:storage-size` | `src/total-storage-size.ts` | Sums the byte size of everything the account stores |
| `pnpm query:activities` | `src/blob-activities.ts` | Shows the account's blob activity log |
| `pnpm query:activities-count` | `src/blob-activities-count.ts` | Counts activity records for the account |
| `pnpm query:placement-groups` | `src/placement-groups.ts` | Lists a page of network placement group slots |
| `pnpm query:placement-group-count` | `src/placement-group-count.ts` | Counts placement group slots network-wide |
| `pnpm query:storage-providers` | `src/storage-providers.ts` | Lists the storage providers serving the network |
| `pnpm delete-blob` | `src/delete-blob.ts` | ⚠️ Permanently deletes a blob (mutates on-chain state) |

Each runs the TypeScript file directly using tsx with the environment variables from your `.env` file.

### Alternative Execution

You can also run any of the files directly using tsx:

```bash
npx tsx --env-file=.env src/account-blobs.ts
```

### Customizing the queries

Two scripts act on a specific blob name, set as a constant at the top of the file:

```ts
const BLOB_NAME = "sdk-test.txt";
```

Change that to a blob your account actually owns before running `query:metadata` or `delete-blob`.

The count and list queries accept a Hasura-style `where` clause, so they can be narrowed beyond "everything this account owns". For example, to count only blobs owned by someone else:

```ts
const count = await client.getBlobsCount({
where: { owner: { _eq: "0x1" } },
});
```

## How It Works

1. **Environment Validation**: Each script validates that the environment variables it needs are set
2. **Client Initialization**: Creates the client for the data being queried — `ShelbyBlobClient` for blobs and activities, `ShelbyPlacementGroupClient` for placement groups, `ShelbyMetadataClient` for storage providers
3. **Account Setup**: Parses the account address from the environment variable (`delete-blob` builds a signing account from a private key instead)
4. **Query**: Issues a single SDK call — the list and count variants are separate methods, so counts never transfer the underlying rows
5. **Display Results**: Prints scalars directly, and object results via `console.dir` with unlimited depth

## Output

Counting queries print a single line:

```
Blob count: 3
```

```
Total blob size: 1539615 bytes
```

Listing queries print a summary line followed by the full objects:

```
Account: 0x1234...abcd
Found 3 blob(s)
[
{
name: 'whitepaper.pdf',
size: 1024567,
expirationMicros: 1760628600000000,
...
},
...
]
```

And `delete-blob` prints the submitted transaction:

```
Delete transaction submitted!
Hash: 0x9f8e...
```

## Troubleshooting

### Common Issues

1. **SHELBY_ACCOUNT_ADDRESS is not set in .env**
- Ensure you have created a `.env` file with the required variables
- Check that the variable name is spelled correctly
- Verify the account address format is valid

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. **No blob named "..." found**
- `getBlobMetadata` returns `undefined` rather than throwing when the blob doesn't exist
- Update the `BLOB_NAME` constant to a blob the account actually owns
- Run `pnpm query:blobs` to see which names are available

4. **Empty results from every query**
- Verify that the account address contains uploaded blobs
- Check that you're using the correct account address
- Ensure blobs haven't expired

5. **Rate limit exceeded (429)**
- Wait a moment before retrying
- Consider implementing exponential backoff for production use

6. **Server errors (500)**
- This indicates an issue with the Shelby service
- Contact Shelby support if this occurs repeatedly
43 changes: 43 additions & 0 deletions apps/sdk-queries/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@shelby-protocol/query-example",
"version": "1.0.0",
"description": "An example app to demonstrate querying blobs, activities, and network metadata using the Shelby SDK",
"type": "module",
"scripts": {
"query:blobs": "tsx --env-file=.env src/account-blobs.ts",
"query:metadata": "tsx --env-file=.env src/blob-metadata.ts",
"query:blob-count": "tsx --env-file=.env src/blob-count.ts",
"query:storage-size": "tsx --env-file=.env src/total-storage-size.ts",
"query:activities": "tsx --env-file=.env src/blob-activities.ts",
"query:activities-count": "tsx --env-file=.env src/blob-activities-count.ts",
"query:placement-groups": "tsx --env-file=.env src/placement-groups.ts",
"query:placement-group-count": "tsx --env-file=.env src/placement-group-count.ts",
"query:storage-providers": "tsx --env-file=.env src/storage-providers.ts",
"delete-blob": "tsx --env-file=.env src/delete-blob.ts",
"lint": "biome check .",
"fmt": "biome check . --write"
},
"keywords": [
"shelby",
"sdk",
"blob",
"storage",
"query",
"indexer"
],
"author": "Akasha <akasha@shelby.xyz>",
"license": "MIT",
"dependencies": {
"@aptos-labs/ts-sdk": "^5.2.1",
"@shelby-protocol/sdk": "^0.3.1"
},
"devDependencies": {
"@biomejs/biome": "2.2.4",
"tsx": "^4.20.5",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
},
"engines": {
"node": ">=22"
}
}
26 changes: 26 additions & 0 deletions apps/sdk-queries/src/account-blobs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AccountAddress, Network } from "@aptos-labs/ts-sdk";
import { ShelbyBlobClient } from "@shelby-protocol/sdk/node";

if (!process.env.SHELBY_API_KEY) {
throw new Error("Missing SHELBY_API_KEY");
}
if (!process.env.SHELBY_ACCOUNT_ADDRESS) {
throw new Error("Missing SHELBY_ACCOUNT_ADDRESS");
}

// 1) Initialize a blob client (auth via API key; target shelbynet).
const client = new ShelbyBlobClient({
network: Network.SHELBYNET,
apiKey: process.env.SHELBY_API_KEY, // ensure .env is loaded
});

// 2) Parse the account address you'll query.
const account = AccountAddress.fromString(process.env.SHELBY_ACCOUNT_ADDRESS);

// 3) Ask the indexer for every blob owned by that account.
const blobs = await client.getAccountBlobs({ account });

// 4) Print the full metadata for each blob.
console.log(`Account: ${account.toString()}`);
console.log(`Found ${blobs.length} blob(s)`);
console.dir(blobs, { depth: null });
25 changes: 25 additions & 0 deletions apps/sdk-queries/src/blob-activities-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { AccountAddress, Network } from "@aptos-labs/ts-sdk";
import { ShelbyBlobClient } from "@shelby-protocol/sdk/node";

if (!process.env.SHELBY_API_KEY) {
throw new Error("Missing SHELBY_API_KEY");
}
if (!process.env.SHELBY_ACCOUNT_ADDRESS) {
throw new Error("Missing SHELBY_ACCOUNT_ADDRESS");
}

// 1) Initialize a blob client (auth via API key; target shelbynet).
const client = new ShelbyBlobClient({
network: Network.SHELBYNET,
apiKey: process.env.SHELBY_API_KEY, // ensure .env is loaded
});

// 2) Parse the account address you'll query.
const account = AccountAddress.fromString(process.env.SHELBY_ACCOUNT_ADDRESS);

// 3) Count activity records without transferring them.
const count = await client.getBlobActivitiesCount({
where: { owner: { _eq: account.toString() } },
});

console.log(`Blob activity count: ${count}`);
26 changes: 26 additions & 0 deletions apps/sdk-queries/src/blob-activities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AccountAddress, Network } from "@aptos-labs/ts-sdk";
import { ShelbyBlobClient } from "@shelby-protocol/sdk/node";

if (!process.env.SHELBY_API_KEY) {
throw new Error("Missing SHELBY_API_KEY");
}
if (!process.env.SHELBY_ACCOUNT_ADDRESS) {
throw new Error("Missing SHELBY_ACCOUNT_ADDRESS");
}

// 1) Initialize a blob client (auth via API key; target shelbynet).
const client = new ShelbyBlobClient({
network: Network.SHELBYNET,
apiKey: process.env.SHELBY_API_KEY, // ensure .env is loaded
});

// 2) Parse the account address you'll query.
const account = AccountAddress.fromString(process.env.SHELBY_ACCOUNT_ADDRESS);

// 3) Fetch the activity log (uploads, deletions, extensions) for the account.
const activities = await client.getBlobActivities({
where: { owner: { _eq: account.toString() } },
});

console.log(`Found ${activities.length} activity record(s)`);
console.dir(activities, { depth: null });
26 changes: 26 additions & 0 deletions apps/sdk-queries/src/blob-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AccountAddress, Network } from "@aptos-labs/ts-sdk";
import { ShelbyBlobClient } from "@shelby-protocol/sdk/node";

if (!process.env.SHELBY_API_KEY) {
throw new Error("Missing SHELBY_API_KEY");
}
if (!process.env.SHELBY_ACCOUNT_ADDRESS) {
throw new Error("Missing SHELBY_ACCOUNT_ADDRESS");
}

// 1) Initialize a blob client (auth via API key; target shelbynet).
const client = new ShelbyBlobClient({
network: Network.SHELBYNET,
apiKey: process.env.SHELBY_API_KEY, // ensure .env is loaded
});

// 2) Parse the account address you'll query.
const account = AccountAddress.fromString(process.env.SHELBY_ACCOUNT_ADDRESS);

// 3) Count blobs via an indexer filter instead of fetching them all.
// The `where` clause is a Hasura-style boolean expression.
const count = await client.getBlobsCount({
where: { owner: { _eq: account.toString() } },
});

console.log(`Blob count: ${count}`);
32 changes: 32 additions & 0 deletions apps/sdk-queries/src/blob-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AccountAddress, Network } from "@aptos-labs/ts-sdk";
import { ShelbyBlobClient } from "@shelby-protocol/sdk/node";

// The blob name as stored in Shelby (must match what you uploaded or already have).
const BLOB_NAME = "sdk-test.txt";

if (!process.env.SHELBY_API_KEY) {
throw new Error("Missing SHELBY_API_KEY");
}
if (!process.env.SHELBY_ACCOUNT_ADDRESS) {
throw new Error("Missing SHELBY_ACCOUNT_ADDRESS");
}

// 1) Initialize a blob client (auth via API key; target shelbynet).
const client = new ShelbyBlobClient({
network: Network.SHELBYNET,
apiKey: process.env.SHELBY_API_KEY, // ensure .env is loaded
});

// 2) Parse the account address the blob lives under.
const account = AccountAddress.fromString(process.env.SHELBY_ACCOUNT_ADDRESS);

// 3) Look up the on-chain metadata for a single blob.
// ⚠️ Returns `undefined` when no blob with that name exists.
const metadata = await client.getBlobMetadata({ account, name: BLOB_NAME });

// 4) Print the metadata (or say so when the blob isn't there).
if (!metadata) {
console.log(`No blob named "${BLOB_NAME}" found for ${account.toString()}`);
} else {
console.dir(metadata, { depth: null });
}
37 changes: 37 additions & 0 deletions apps/sdk-queries/src/delete-blob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// ⚠️ This script MUTATES on-chain state: it permanently deletes a blob.
// Unlike the other scripts here it signs a transaction, so it needs a
// private key rather than just an account address.

import { Account, Ed25519PrivateKey, Network } from "@aptos-labs/ts-sdk";
import { ShelbyBlobClient } from "@shelby-protocol/sdk/node";

// The blob to delete, as stored in Shelby.
const BLOB_NAME = "sdk-test.txt";

if (!process.env.SHELBY_API_KEY) {
throw new Error("Missing SHELBY_API_KEY");
}
if (!process.env.SHELBY_ACCOUNT_PRIVATE_KEY) {
throw new Error("Missing SHELBY_ACCOUNT_PRIVATE_KEY");
}

// 1) Initialize a blob client (auth via API key; target shelbynet).
const client = new ShelbyBlobClient({
network: Network.SHELBYNET,
apiKey: process.env.SHELBY_API_KEY, // ensure .env is loaded
});

// 2) Create an Aptos account object from your private key.
// ⚠️ This must be the *same account* that uploaded the blob.
const signer = Account.fromPrivateKey({
privateKey: new Ed25519PrivateKey(process.env.SHELBY_ACCOUNT_PRIVATE_KEY),
});

// 3) Submit the delete transaction.
const { transaction } = await client.deleteBlob({
account: signer,
blobName: BLOB_NAME,
});

console.log("Delete transaction submitted!");
console.log("Hash:", transaction.hash);
Loading