Skip to content
Merged
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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@oasisprotocol/sapphire-paratime": "^1.3.2",
"@oceanprotocol/contracts": "^2.5.0",
"@oceanprotocol/ddo-js": "^0.3.0",
"@oceanprotocol/lib": "^8.0.4",
"@oceanprotocol/lib": "^8.0.6",
"commander": "^13.1.0",
"cross-fetch": "^3.1.5",
"crypto-js": "^4.1.1",
Expand Down
92 changes: 85 additions & 7 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,36 +69,50 @@
denyDialMultiaddr: () => false,
},
},
} as any);

Check warning on line 72 in src/cli.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
console.log(
chalk.cyan("libp2p node started. Waiting for peer connections...")
);

// Wait for at least one active P2P connection before running commands
// Wait for the TARGET peer (the one in NODE_URL) to be connected,
// not just any bootstrap peer — otherwise signed commands fail with
// "Cannot reach peer ...".
const targetPeerId = isFullMultiaddr
? nodeUrl.split("/p2p/").pop()!
: nodeUrl;
const maxWait = 20_000;
const interval = 500;
let waited = 0;
const libp2p = (ProviderInstance as any).p2pProvider?.libp2pNode;

Check warning on line 86 in src/cli.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const isTargetConnected = () =>
(libp2p?.getPeers() ?? []).some(
(p: { toString(): string }) => p.toString() === targetPeerId
);
while (waited < maxWait) {
const conns = libp2p?.getConnections()?.length ?? 0;
if (conns > 0) {
if (isTargetConnected()) {
const total = libp2p?.getConnections()?.length ?? 0;
console.log(
chalk.green(`Connected to ${conns} peer(s) in ${waited}ms`)
chalk.green(
`Connected to target peer ${targetPeerId.slice(0, 12)}… in ${waited}ms (total peers: ${total})`
)
);
break;
}
await new Promise((r) => setTimeout(r, interval));
waited += interval;
if (waited % 3000 === 0) {
const total = libp2p?.getConnections()?.length ?? 0;
console.log(
chalk.yellow(` Still waiting for peers... (${waited / 1000}s)`)
chalk.yellow(
` Waiting for target peer ${targetPeerId.slice(0, 12)}… (${waited / 1000}s, ${total} other peer(s))`
)
);
}
}
if ((libp2p?.getConnections()?.length ?? 0) === 0) {
if (!isTargetConnected()) {
console.error(
chalk.red(
`No P2P peers connected after ${maxWait / 1000}s. Commands may fail.`
`Target peer ${targetPeerId} not reachable after ${maxWait / 1000}s. Commands will fail.`
)
);
}
Expand Down Expand Up @@ -802,5 +816,69 @@
]);
});

program
.command("createBucket")
.description("Create a new persistent-storage bucket gated by a single access list (chain inferred from RPC)")
.argument("<accessListAddress>", "Access list contract address (0x…)")
.action(async (accessListAddress) => {
const { signer, chainId } = await initializeSigner();
const commands = new Commands(signer, chainId);
await commands.createBucket([null, accessListAddress]);
});

program
.command("addFileToBucket")
.description("Upload a local file into a bucket")
.argument("<bucketId>", "Bucket id")
.argument("<filePath>", "Path to local file")
.argument("[fileName]", "Name under which to store the file (defaults to basename)")
.action(async (bucketId, filePath, fileName) => {
const { signer, chainId } = await initializeSigner();
const commands = new Commands(signer, chainId);
await commands.addFileToBucket([null, bucketId, filePath, fileName]);
});

program
.command("listBuckets")
.description("List buckets owned by an address (defaults to signer)")
.option("-o, --owner <address>", "Owner address")
.action(async (options) => {
const { signer, chainId } = await initializeSigner();
const commands = new Commands(signer, chainId);
await commands.listBuckets([null, options.owner]);
});

program
.command("listFilesInBucket")
.description("List files in a bucket")
.argument("<bucketId>", "Bucket id")
.action(async (bucketId) => {
const { signer, chainId } = await initializeSigner();
const commands = new Commands(signer, chainId);
await commands.listFilesInBucket([null, bucketId]);
});

program
.command("getFileObject")
.description("Get the file-object descriptor for a file in a bucket")
.argument("<bucketId>", "Bucket id")
.argument("<fileName>", "File name")
.action(async (bucketId, fileName) => {
const { signer, chainId } = await initializeSigner();
const commands = new Commands(signer, chainId);
await commands.getFileObject([null, bucketId, fileName]);
});

program
.command("deleteFile")
.description("Delete a file from a bucket")
.argument("<bucketId>", "Bucket id")
.argument("<fileName>", "File name")
.action(async (bucketId, fileName) => {
const { signer, chainId } = await initializeSigner();
const commands = new Commands(signer, chainId);
await commands.deleteFile([null, bucketId, fileName]);
});

return program;
}
130 changes: 130 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1707,7 +1707,7 @@
)
);
removedCount++;
} catch (e: any) {

Check warning on line 1710 in src/commands.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
console.log(
chalk.yellow(
`⚠ Could not remove token at index ${index} for user ${user}: ${e.message}`
Expand Down Expand Up @@ -1795,4 +1795,134 @@
console.error(chalk.red("Error downloading node logs: "), error);
}
}

public async createBucket(args: string[]): Promise<void> {
try {
const accessListAddress = args[1];
if (!accessListAddress) {
console.error(chalk.red("accessListAddress is required"));
return;
}
if (!/^0x[a-fA-F0-9]{40}$/.test(accessListAddress)) {
console.error(chalk.red(`Invalid access list address: ${accessListAddress}`));
return;
}

const { chainId } = await this.signer.provider.getNetwork();
const accessLists = [{ [String(chainId)]: [accessListAddress] }];
const result = await ProviderInstance.createPersistentStorageBucket(
this.oceanNodeUrl,
this.signer,
{ accessLists }
);
console.log(chalk.green("Bucket created."));
console.log(util.inspect(result, false, null, true));
} catch (error) {
console.error(chalk.red("Error creating bucket: "), error);
}
}

public async addFileToBucket(args: string[]): Promise<void> {
try {
const bucketId = args[1];
const filePath = args[2];
const fileName = args[3] || (filePath ? path.basename(filePath) : undefined);
if (!bucketId || !filePath) {
console.error(chalk.red("bucketId and filePath are required"));
return;
}
if (!fs.existsSync(filePath)) {
console.error(chalk.red(`File not found: ${filePath}`));
return;
}

const stream = fs.createReadStream(filePath);
const result = await ProviderInstance.uploadPersistentStorageFile(
this.oceanNodeUrl,
this.signer,
bucketId,
fileName,
stream as unknown as AsyncIterable<Uint8Array>
);
console.log(chalk.green(`File '${fileName}' uploaded to bucket ${bucketId}.`));
console.log(util.inspect(result, false, null, true));
} catch (error) {
console.error(chalk.red("Error uploading file: "), error);
}
}

public async listBuckets(args: string[]): Promise<void> {
try {
const owner = args[1] || (await this.signer.getAddress());
const buckets = await ProviderInstance.getPersistentStorageBuckets(
this.oceanNodeUrl,
this.signer,
owner
);
console.log(chalk.cyan(`Buckets owned by ${owner}:`));
console.log(util.inspect(buckets, false, null, true));
} catch (error) {
console.error(chalk.red("Error listing buckets: "), error);
}
}

public async listFilesInBucket(args: string[]): Promise<void> {
try {
const bucketId = args[1];
if (!bucketId) {
console.error(chalk.red("bucketId is required"));
return;
}
const files = await ProviderInstance.listPersistentStorageFiles(
this.oceanNodeUrl,
this.signer,
bucketId
);
console.log(chalk.cyan(`Files in bucket ${bucketId}:`));
console.log(util.inspect(files, false, null, true));
} catch (error) {
console.error(chalk.red("Error listing files: "), error);
}
}

public async getFileObject(args: string[]): Promise<void> {
try {
const bucketId = args[1];
const fileName = args[2];
if (!bucketId || !fileName) {
console.error(chalk.red("bucketId and fileName are required"));
return;
}
const fileObject = await ProviderInstance.getPersistentStorageFileObject(
this.oceanNodeUrl,
this.signer,
bucketId,
fileName
);
console.log(JSON.stringify(fileObject, null, 2));
} catch (error) {
console.error(chalk.red("Error getting file object: "), error);
}
}

public async deleteFile(args: string[]): Promise<void> {
try {
const bucketId = args[1];
const fileName = args[2];
if (!bucketId || !fileName) {
console.error(chalk.red("bucketId and fileName are required"));
return;
}
const result = await ProviderInstance.deletePersistentStorageFile(
this.oceanNodeUrl,
this.signer,
bucketId,
fileName
);
console.log(chalk.green(`File '${fileName}' deleted from bucket ${bucketId}.`));
console.log(util.inspect(result, false, null, true));
} catch (error) {
console.error(chalk.red("Error deleting file: "), error);
}
}
}
Loading
Loading