
Security News
The Next Open Source Security Race: Triage at Machine Speed
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.
@inkwell.ar/sdk
Advanced tools
SDK for interacting with the Inkwell Blog CRUD AO process using aoconnect for deployment and interactions
A TypeScript SDK for interacting with the Inkwell Blog CRUD AO process using aoconnect. Published as @inkwell.ar/sdk.
npm install @inkwell.ar/sdk
Or using yarn:
yarn add @inkwell.ar/sdk
The SDK is designed to work in both browser and Node.js environments:
The SDK uses aoconnect for deployment in all environments, ensuring consistent behavior.
The SDK works in browsers for all operations including deployment:
import { InkwellBlogSDK, BlogRegistrySDK } from '@inkwell.ar/sdk';
// Deploy a new blog (works in browser!)
// No wallet needed if browser wallet is connected
const result = await InkwellBlogSDK.deploy({
name: 'my-blog'
// wallet: wallet // Optional - will use browser wallet if not provided
});
// Initialize with the deployed blog
const blogSDK = new InkwellBlogSDK({
processId: result.processId
});
// Initialize the registry SDK
const registry = new BlogRegistrySDK();
// Get all posts
const response = await blogSDK.getAllPosts({ ordered: true });
if (response.success) {
console.log('Posts:', response.data);
}
// Check user permissions
const canEdit = await registry.canEditBlog('wallet-address', result.processId);
const canAdmin = await registry.canAdminBlog('wallet-address', result.processId);
// Get user's blogs
const userBlogs = await registry.getWalletBlogs('wallet-address');
For Node.js environments (same functionality as browser, uses aoconnect):
# Deploy the registry (requires wallet)
npm run deploy:registry [wallet-path]
This will:
src/config/registry.tsimport { InkwellBlogSDK } from '@inkwell.ar/sdk';
// Deploy a new blog (automatically configured with registry)
// No wallet needed if browser wallet is connected
const result = await InkwellBlogSDK.deploy({
name: 'my-blog'
// wallet: wallet // Optional - will use browser wallet if not provided
});
console.log('Blog deployed:', result.processId);
import { InkwellBlogSDK, BlogRegistrySDK } from '@inkwell.ar/sdk';
// Initialize the blog SDK
const blogSDK = new InkwellBlogSDK({
processId: result.processId
});
// Initialize the registry SDK (uses hardcoded registry process ID)
const registry = new BlogRegistrySDK();
// Get all posts
const response = await blogSDK.getAllPosts({ ordered: true });
if (response.success) {
console.log('Posts:', response.data);
}
// Check user permissions
const canEdit = await registry.canEditBlog('wallet-address', result.processId);
const canAdmin = await registry.canAdminBlog('wallet-address', result.processId);
// Get user's blogs
const userBlogs = await registry.getWalletBlogs('wallet-address');
import { InkwellBlogSDK } from '@inkwell.ar/sdk';
const blogSDK = new InkwellBlogSDK({
processId: string, // Required: Your AO process ID
wallet?: any, // Optional: Arweave wallet for authenticated operations
aoconnect?: any // Optional: Custom aoconnect instance
});
Browser Wallet Support: In browser environments, if no wallet is provided, the SDK will automatically use globalThis.arweaveWallet if available.
The registry SDK provides read-only access to the centralized permission system:
import { BlogRegistrySDK } from '@inkwell.ar/sdk';
const registry = new BlogRegistrySDK();
// Check permissions
const canEdit = await registry.canEditBlog('wallet-address', 'blog-process-id');
const canAdmin = await registry.canAdminBlog('wallet-address', 'blog-process-id');
// Get user's blogs
const userBlogs = await registry.getWalletBlogs('wallet-address');
const adminBlogs = await registry.getAdminBlogs('wallet-address');
const editableBlogs = await registry.getEditableBlogs('wallet-address');
// Get registry statistics
const stats = await registry.getRegistryStats();
Security Note: The registry SDK is read-only. Only blog processes can modify permissions in the registry.
getInfo()Get blog information including name, author, and details.
const response = await blogSDK.getInfo();
// Returns: { name, author, blogTitle, blogDescription, blogLogo, details }
getAllPosts(options?)Get all blog posts.
const response = await blogSDK.getAllPosts({
ordered: true // Optional: Sort by published_at (newest first)
});
getPost(options)Get a specific post by ID.
const response = await blogSDK.getPost({
id: 1
});
getUserRoles()Get roles for the current wallet.
const response = await blogSDK.getUserRoles();
createPost(options)Create a new blog post.
const response = await blogSDK.createPost({
data: {
title: 'My Blog Post',
description: 'A brief description',
body: 'Full post content...',
published_at: Date.now(),
last_update: Date.now(),
labels: ['tag1', 'tag2'],
authors: ['@author1', '@author2']
},
wallet: yourWallet // Optional: will use browser wallet if not provided
});
updatePost(options)Update an existing blog post.
const response = await blogSDK.updatePost({
id: 1,
data: {
title: 'Updated Title',
description: 'Updated description',
// ... other fields
},
wallet: yourWallet // Optional: will use browser wallet if not provided
});
deletePost(options)Delete a blog post.
const response = await blogSDK.deletePost({
id: 1,
wallet: yourWallet // Optional: will use browser wallet if not provided
});
addEditors(options)Add new editors to the blog.
const response = await blogSDK.addEditors({
accounts: ['editor-address-1', 'editor-address-2'],
wallet: yourWallet // Optional: will use browser wallet if not provided
});
removeEditors(options)Remove editors from the blog.
const response = await blogSDK.removeEditors({
accounts: ['editor-address-to-remove'],
wallet: yourWallet // Optional: will use browser wallet if not provided
});
addAdmins(options)Add new admins to the blog.
const response = await blogSDK.addAdmins({
accounts: ['admin-address-1', 'admin-address-2'],
wallet: yourWallet // Optional: will use browser wallet if not provided
});
removeAdmins(options)Remove admins from the blog.
const response = await blogSDK.removeAdmins({
accounts: ['admin-address-to-remove'],
wallet: yourWallet // Optional: will use browser wallet if not provided
});
getEditors()Get all current editors.
const response = await blogSDK.getEditors();
getAdmins()Get all current admins.
const response = await blogSDK.getAdmins();
setBlogDetails(options)Set blog details (title, description, logo). Admin role required.
const response = await blogSDK.setBlogDetails({
data: {
title: 'My Blog Title',
description: 'My blog description',
logo: 'https://example.com/logo.png'
},
wallet: yourWallet // Optional: will use browser wallet if not provided
});
interface BlogPost {
id: number;
title: string;
description: string;
body?: string;
published_at: number;
last_update: number;
labels?: string[];
authors: string[];
}
interface ApiResponse<T = any> {
success: boolean;
data: T | string; // T for successful operations, string for error messages
}
interface CreatePostData {
title: string;
description: string;
body?: string;
published_at: number;
last_update: number;
labels?: string[];
authors: string[];
}
interface BlogDetails {
title: string;
description: string;
logo: string;
}
interface BlogInfo {
name: string;
author: string;
blogTitle: string;
blogDescription: string;
blogLogo: string;
details: BlogDetails;
}
interface UpdateBlogDetailsData {
title?: string;
description?: string;
logo?: string;
}
The SDK includes built-in deployment functionality using ao-deploy.
import { InkwellBlogSDK } from '@inkwell.ar/sdk';
// Deploy a new blog process
const deployResult = await InkwellBlogSDK.deploy({
name: 'my-blog',
wallet: yourWallet,
contractPath: './lua-process/inkwell_blog.lua',
luaPath: './lua-process/?.lua',
tags: [
{ name: 'Blog-Name', value: 'My Personal Blog' },
{ name: 'Author', value: '@myhandle' }
],
minify: true
});
console.log('Process ID:', deployResult.processId);
console.log('View at:', `https://www.ao.link/#/entity/${deployResult.processId}`);
// Initialize SDK with the new process
const blogSDK = new InkwellBlogSDK({
processId: deployResult.processId,
wallet: yourWallet
});
interface DeployOptions {
name?: string; // Process name (default: 'inkwell-blog')
wallet?: string | any; // Arweave wallet
contractPath?: string; // Path to process file
luaPath?: string; // Path to Lua modules
tags?: Array<{ name: string; value: string }>; // Custom tags
retry?: { count: number; delay: number }; // Retry configuration
minify?: boolean; // Minify contract (default: true)
contractTransformer?: (source: string) => string; // Custom source transformer
onBoot?: boolean; // Load on boot (default: false)
silent?: boolean; // Disable logging (default: false)
blueprints?: string[]; // Blueprints to use
forceSpawn?: boolean; // Force new process (default: false)
}
import { InkwellBlogSDK } from '@inkwell.ar/sdk';
const blogSDK = new InkwellBlogSDK({
processId: 'your-process-id'
});
// Get blog information
const infoResponse = await blogSDK.getInfo();
if (infoResponse.success) {
const info = infoResponse.data;
console.log(`Blog: ${info.name} by ${info.author}`);
console.log(`Title: ${info.blogTitle}`);
console.log(`Description: ${info.blogDescription}`);
}
// Get all posts
const postsResponse = await blogSDK.getAllPosts({ ordered: true });
if (postsResponse.success) {
postsResponse.data.forEach(post => {
console.log(`${post.title} by ${post.authors.join(', ')}`);
});
}
import { InkwellBlogSDK } from '@inkwell.ar/sdk';
import Arweave from 'arweave';
const arweave = Arweave.init();
// Load or generate wallet (automatically saves to wallet.json)
let wallet: any;
const walletPath = 'wallet.json';
try {
const fs = require('fs');
if (fs.existsSync(walletPath)) {
console.log('Loading existing wallet...');
const walletData = fs.readFileSync(walletPath, 'utf8');
wallet = JSON.parse(walletData);
} else {
console.log('Generating new wallet...');
wallet = await arweave.wallets.generate();
fs.writeFileSync(walletPath, JSON.stringify(wallet, null, 2));
console.log('Wallet saved to wallet.json');
}
} catch (error) {
console.log('Generating new wallet due to error...');
wallet = await arweave.wallets.generate();
}
const blogSDK = new InkwellBlogSDK({
processId: 'your-process-id',
wallet: wallet
});
// Set blog details (requires Admin role) const blogDetailsResponse = await blogSDK.setBlogDetails({ data: { title: 'My Personal Blog', description: 'A blog about technology and life', logo: 'https://example.com/logo.png' }, wallet: wallet });
// Create a new post (requires Editor role) const createResponse = await blogSDK.createPost({ data: { title: 'My First Post', description: 'Hello World!', body: 'This is my first blog post.', published_at: Date.now(), last_update: Date.now(), authors: ['@myhandle'] }, wallet: wallet });
### Error Handling
```typescript
try {
const response = await blogSDK.createPost({
data: invalidData,
wallet: wallet
});
if (!response.success) {
console.error('Operation failed:', response.data);
}
} catch (error) {
console.error('Unexpected error:', error);
}
The SDK examples automatically handle wallet management:
wallet.json in the project root⚠️ Security Note: The wallet.json file contains your private keys. Keep it secure and never commit it to version control.
In browser environments, the SDK supports automatic browser wallet detection:
// No wallet needed - SDK will use browser wallet automatically
const response = await blogSDK.createPost({
data: {
title: 'My Post',
description: 'Post description',
body: 'Post content...',
published_at: Date.now(),
last_update: Date.now(),
authors: ['@myhandle']
}
// wallet parameter is optional in browser
});
Browser Wallet Support: The SDK automatically detects and uses globalThis.arweaveWallet if available, making wallet management seamless in browser applications.
The blog uses a role-based access control system:
const rolesResponse = await blogSDK.getUserRoles();
if (rolesResponse.success) {
const roles = rolesResponse.data;
if (roles.includes('EDITOR_ROLE')) {
// User can create/edit posts
}
if (roles.includes('DEFAULT_ADMIN_ROLE')) {
// User can manage roles
}
}
The SDK automatically attempts to retrieve the actual result of message operations:
This provides the best of both worlds:
The SDK uses a unified parsing approach that handles both dryrun and message responses:
Write operations can return either the actual data or a success message:
// createPost and updatePost can return:
type CreatePostResponse = ApiResponse<BlogPost | string>;
// addEditors, removeEditors, addAdmins, removeAdmins can return:
type RoleResponse = ApiResponse<RoleUpdateResult[] | string>;
// setBlogDetails can return:
type BlogDetailsResponse = ApiResponse<BlogDetails | string>;
// deletePost always returns:
type DeleteResponse = ApiResponse<string>;
The RoleUpdateResult type represents the result of role management operations:
type RoleUpdateResult = [string, boolean, string?];
// [account, success, error?]
account: The wallet address that was processedsuccess: Whether the operation succeedederror: Optional error message if the operation failedconst response = await blogSDK.createPost({ data: postData });
if (response.success) {
if (typeof response.data === 'object' && response.data !== null) {
// Got the actual post data
const post = response.data as BlogPost;
console.log(`Post ID: ${post.id}`);
console.log(`Title: ${post.title}`);
} else {
// Got a success message
console.log(`Message: ${response.data}`);
}
}
// For role management operations:
const roleResponse = await blogSDK.addEditors({ accounts: ['address1'], wallet });
if (roleResponse.success) {
if (Array.isArray(roleResponse.data)) {
// Got the actual role update results
const results = roleResponse.data as RoleUpdateResult[];
results.forEach(([account, success, error]) => {
if (success) {
console.log(`✅ Successfully added editor: ${account}`);
} else {
console.log(`❌ Failed to add editor ${account}: ${error}`);
}
});
} else {
// Got a success message
console.log(`Message: ${roleResponse.data}`);
}
}
The SDK provides comprehensive error handling:
All methods return an ApiResponse object with:
success: Boolean indicating if the operation succeededdata: The result data or error messageSee src/examples/basic-usage.ts for a complete example of deploying and using a blog with registry integration.
See src/examples/basic-registry-usage.ts for examples of:
See src/examples/browser-deployment.ts for examples of:
npm run build
npm run dev
npm test
npm run lint
Before publishing, make sure to:
Build the package:
npm run build
Test the build:
npm test
Check package contents:
npm pack --dry-run
Publish to npm:
npm publish
The prepublishOnly script will automatically run the build before publishing.
The npm package includes:
dist/.d.ts fileslua-process/ directory with your AO process filesThe package excludes:
src/)MIT
FAQs
SDK for interacting with the Inkwell Blog CRUD AO process using aoconnect for deployment and interactions
The npm package @inkwell.ar/sdk receives a total of 0 weekly downloads. As such, @inkwell.ar/sdk popularity was classified as not popular.
We found that @inkwell.ar/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Claude Opus 4.6 has uncovered more than 500 open source vulnerabilities, raising new considerations for disclosure, triage, and patching at scale.

Research
/Security News
Malicious dYdX client packages were published to npm and PyPI after a maintainer compromise, enabling wallet credential theft and remote code execution.

Security News
gem.coop is testing registry-level dependency cooldowns to limit exposure during the brief window when malicious gems are most likely to spread.