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
20 changes: 20 additions & 0 deletions skills/arweave/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Upload files and websites to permanent storage on Arweave, and manage ArNS (Arwe
| "use arweave to upload `<dir>`" | `upload-site` |
| "use arweave to attach `<txId>` to `<name>`" | `attach` |
| "use arweave to query transactions" | `query` |
| "use arweave to generate a wallet" | `generate-wallet` |

## Wallet Handling

Expand Down Expand Up @@ -84,6 +85,20 @@ node skills/arweave/index.mjs attach "<txId>" "<name>" --network testnet --walle
node skills/arweave/index.mjs attach "<txId>" "<name>" --ario-process "<processId>" --wallet "..." --yes
```

### Generate a New Wallet

```sh
node skills/arweave/index.mjs generate-wallet
node skills/arweave/index.mjs generate-wallet "./my-wallet.json"
```

- Generates an RSA-4096 Arweave wallet (JWK) using Node.js built-in `crypto.subtle`
- Defaults to `./wallet.json` if no path given
- Prompts before overwriting an existing file (use `--yes` to skip)
- Prints the wallet address after generation
- **No network calls needed** - purely local key generation
- The new wallet starts with zero balance; fund it before uploading

## Output Handling

After successful upload, report back:
Expand Down Expand Up @@ -215,4 +230,9 @@ node skills/arweave/index.mjs attach "<txId>" "hello_rakis" --network testnet --

# Attach using specific ARIO process
node skills/arweave/index.mjs attach "<txId>" "hello_rakis" --ario-process testnet --wallet "/path/to/wallet.json" --yes

# Generate a new wallet
node skills/arweave/index.mjs generate-wallet
node skills/arweave/index.mjs generate-wallet "./my-wallet.json"
node skills/arweave/index.mjs generate-wallet "./my-wallet.json" --yes
```
338 changes: 171 additions & 167 deletions skills/arweave/index.mjs

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import * as readline from 'readline';
import { NodeARx } from '@permaweb/arx/node';
import { ArweaveSigner, ARIO, ANT, AOProcess, ARIO_MAINNET_PROCESS_ID, ARIO_TESTNET_PROCESS_ID } from '@ar.io/sdk/node';
Expand Down Expand Up @@ -243,6 +244,7 @@ Commands:
upload-site <dir> [--index file] Upload a directory as a static site
attach <txId> <name> Attach an ArNS name to a transaction
query Query transactions from Arweave GraphQL
generate-wallet [path] Generate a new Arweave wallet (JWK)

Options:
--wallet <path> Path to Arweave wallet keyfile (JWK json)
Expand Down Expand Up @@ -278,6 +280,8 @@ Examples:
arweave-skill query --tag App-Name:MyApp --tag Type:post --block-min 1000000
arweave-skill query --ids <txId1>,<txId2>,<txId3>
arweave-skill query --owner <address> --graphql-endpoint https://arweave.net/graphql
arweave-skill generate-wallet
arweave-skill generate-wallet ./my-wallet.json
`);
}

Expand Down Expand Up @@ -626,6 +630,61 @@ async function handleAttach(args: ParsedArgs): Promise<void> {
}
}

function toB64Url(buf: Uint8Array): string {
return Buffer.from(buf)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}

async function handleGenerateWallet(args: ParsedArgs): Promise<void> {
const outPath = args.target
? path.resolve(args.target)
: path.resolve('wallet.json');

// Safety: don't overwrite an existing wallet unless --yes
if (fs.existsSync(outPath) && !args.yes) {
const confirm = await promptForInput(
`File already exists: ${outPath}\nOverwrite? (y/N): `
);
if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') {
console.log('Aborted.');
process.exit(0);
}
}

console.log('Generating Arweave wallet (RSA-4096)...');

const keyPair = await crypto.subtle.generateKey(
{
name: 'RSA-PSS',
modulusLength: 4096,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: 'SHA-256',
},
true,
['sign', 'verify']
);

const jwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey!);

// Derive address: SHA-256 of the public modulus (n)
const nBytes = Buffer.from(jwk.n!, 'base64');
const addressHash = await crypto.subtle.digest('SHA-256', nBytes);
const address = toB64Url(new Uint8Array(addressHash));

fs.writeFileSync(outPath, JSON.stringify(jwk, null, 2));

console.log('');
console.log('Wallet generated successfully!');
console.log(` File: ${outPath}`);
console.log(` Address: ${address}`);
console.log('');
console.log('IMPORTANT: Back up this file securely. Anyone with this file controls the wallet.');
console.log('NOTE: This is a new wallet with zero balance. Fund it before uploading.');
}

async function handleQuery(args: ParsedArgs): Promise<void> {
// Validate that at least one filter is provided
const hasFilter =
Expand Down Expand Up @@ -749,6 +808,10 @@ async function main(): Promise<void> {
await handleQuery(args);
break;

case 'generate-wallet':
await handleGenerateWallet(args);
break;

default:
console.error(`Unknown command: ${args.command}`);
showHelp();
Expand Down