Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
7 changes: 7 additions & 0 deletions .cursor/rules/cli-command-creation.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
description: CLI command creation
globs:
alwaysApply: true
---

When creating new apis on classes, ask if you should add them to the CLI as well.
9 changes: 9 additions & 0 deletions .cursor/rules/documentation.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Documentation
globs:
alwaysApply: true
---

When creating and modifying APIs, ensure to check the README.md to see if those docs need updating.

When creating those APIs match them with jsdoc appropriately.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ yarn-error.log
tests/wallets
tests/contracts
aos-ant-*.wasm
._*
28 changes: 20 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# [3.20.0-alpha.1](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0...v3.20.0-alpha.1) (2025-09-23)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Align version with PR intent (alpha vs stable).

PR title targets v3.20.0 on main, but header uses 3.20.0-alpha.1. Use stable 3.20.0 (and update compare link) unless you truly intend a pre-release on main.

Apply this diff:

-# [3.20.0-alpha.1](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0...v3.20.0-alpha.1) (2025-09-23)
+# [3.20.0](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0...v3.20.0) (2025-09-23)

Verify tag/compare exist before merging:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
repo="ar-io/ar-io-sdk"
for tag in "v3.20.0" "v3.20.0-alpha.1" "v3.19.0"; do
  echo "Checking tag $tag ..."
  gh api "/repos/$repo/git/refs/tags/$tag" >/dev/null 2>&1 && echo "  ✓ exists" || echo "  ✗ missing"
done
echo "Checking compare URL ..."
gh api "/repos/$repo/compare/v3.19.0...v3.20.0" >/dev/null 2>&1 && echo "  ✓ compare ok" || echo "  ✗ compare missing"

Length of output: 160


Align CHANGELOG header with intended release (compare URL missing)

CHANGELOG.md (lines 1-1) shows 3.20.0-alpha.1 while the PR targets v3.20.0. Verification: tags v3.19.0, v3.20.0, and v3.20.0-alpha.1 exist, but the compare URL v3.19.0...v3.20.0 is missing.

  • If this is a stable release: replace the header and update the compare link — apply the diff below only after the compare URL resolves.
-# [3.20.0-alpha.1](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0...v3.20.0-alpha.1) (2025-09-23)
+# [3.20.0](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0...v3.20.0) (2025-09-23)
  • If this is a pre-release: keep 3.20.0-alpha.1 and make sure PR title/branch indicate a pre-release.

Do not merge until the compare URL (v3.19.0...v3.20.0) is valid.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# [3.20.0-alpha.1](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0...v3.20.0-alpha.1) (2025-09-23)
# [3.20.0](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0...v3.20.0) (2025-09-23)



### Bug Fixes

* **cli:** fix lint in cli ([329759e](https://github.com/ar-io/ar-io-sdk/commit/329759e192788ae664162c5506f6c3ab6d4459aa))
* **cli:** update cli docs ([e1dc35e](https://github.com/ar-io/ar-io-sdk/commit/e1dc35e233933575b7b06d1a4571b4e7122e45cf))
* **cli:** update cli interfaces for transferRecord ([3f553f1](https://github.com/ar-io/ar-io-sdk/commit/3f553f1d8d40a22342573c6c4e4d2280ffb1fa53))
* **cli:** update cli with proper commands for setRecord ([93bf8f5](https://github.com/ar-io/ar-io-sdk/commit/93bf8f5e8bfb43c90f32039cde2668a28ea49108))
* **files:** remove extraneous file ([e63135a](https://github.com/ar-io/ar-io-sdk/commit/e63135a27fe0944ca4102c6e57884fb772f83833))
* **note:** use CAUTION instead of CRITICAL ([3710979](https://github.com/ar-io/ar-io-sdk/commit/3710979f23d13d87a4cd2e19f12cd87994b12012))
* **readme:** update readme with undername ownership rules ([df41a56](https://github.com/ar-io/ar-io-sdk/commit/df41a56124a80c6182be7600f64ebf6ecbc4b702))
* **readme:** use h3 instead of h4 ([5b3232c](https://github.com/ar-io/ar-io-sdk/commit/5b3232ce7f893f8175893b1f88d19465cb8b0c89))
* **todo:** add todo on unused param ([32f502b](https://github.com/ar-io/ar-io-sdk/commit/32f502b6c5ea8f9981c951a3409ccf72a93d3398))


### Features

* **undername ownership:** add undername ownship tooling ([3aa520f](https://github.com/ar-io/ar-io-sdk/commit/3aa520f3ef2ee9606f74bd9692d287e557a64881))

# [3.19.0](https://github.com/ar-io/ar-io-sdk/compare/v3.18.3...v3.19.0) (2025-09-10)


Expand All @@ -21,14 +41,6 @@
* **ant:** add upgrade capability for ANTs ([cf9859a](https://github.com/ar-io/ar-io-sdk/commit/cf9859aa825679dfc2e3fa72e5b89f4962a3d77f))
* **ants:** add .upgrade API to ant class ([22fea51](https://github.com/ar-io/ar-io-sdk/commit/22fea51de7714cc3e00ac2533e6fad209eb5b50b))

# [3.19.0-alpha.10](https://github.com/ar-io/ar-io-sdk/compare/v3.19.0-alpha.9...v3.19.0-alpha.10) (2025-09-10)


### Bug Fixes

* **ant:** fix graphql lookup query ([00753e3](https://github.com/ar-io/ar-io-sdk/commit/00753e3af5e273c4c6d6e55565598ff7a76e0817))
* **io:** fix ant-registry-id param ([7398c6d](https://github.com/ar-io/ar-io-sdk/commit/7398c6d97af0cff49c87aad5f078bc8897844664))

## [3.18.3](https://github.com/ar-io/ar-io-sdk/compare/v3.18.2...v3.18.3) (2025-08-22)


Expand Down
1 change: 1 addition & 0 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ Commands:
set-ant-record [options] Set a record of an ANT process. Deprecated: use set-ant-base-name and set-ant-undername
set-ant-base-name [options] Set the base name of an ANT process
set-ant-undername [options] Set an undername of an ANT process
transfer-record [options] Transfer ownership of a specific record (undername) to another address
set-ant-ticker [options] Set the ticker of an ANT process
set-ant-name [options] Set the name of an ANT process
set-ant-description [options] Set the description of an ANT process
Expand Down
198 changes: 191 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,27 @@ const processId = await ANT.spawn({
description: 'My custom ANT token',
},
});

// Using a custom module ID
const processId = await ANT.spawn({
signer: new ArweaveSigner(jwk),
module: 'FKtQtOOtlcWCW2pXrwWFiCSlnuewMZOHCzhulVkyqBE', // Custom module ID
state: {
name: 'My Custom Module ANT',
ticker: 'CUSTOM',
description: 'ANT using a specific module version',
},
});
```

**CLI Usage:**

```bash
# Spawn ANT with default (latest) module
ar.io spawn-ant --wallet-file wallet.json --name "My ANT" --ticker "MYANT"

# Spawn ANT with custom module ID
ar.io spawn-ant --wallet-file wallet.json --module FKtQtOOtlcWCW2pXrwWFiCSlnuewMZOHCzhulVkyqBE --name "My Custom ANT" --ticker "CUSTOM"
```

**Parameters:**
Expand Down Expand Up @@ -2204,6 +2225,15 @@ const state = await ant.getState();
"transactionId": "2rMLb2uHAyEt7jSu6bXtKx8e-jOfIf7E-DOgQnm8EtU",
"ttlSeconds": 3600
},
"alice": {
"transactionId": "kMk95k_3R8x_7d3wB9tEOiL5v6n8QhR_VnFCh3aeE3f",
"ttlSeconds": 900,
"owner": "alice-wallet-address-123...",
"displayName": "Alice's Portfolio",
"logo": "avatar-tx-id-456...",
"description": "Personal portfolio and blog",
"keywords": ["portfolio", "personal", "blog"]
},
"whitepaper": {
"transactionId": "lNjWn3LpyhKC95Kqe-x8X2qgju0j98MhucdDKK85vc4",
"ttlSeconds": 900
Expand Down Expand Up @@ -2273,11 +2303,19 @@ const records = await ant.getRecords();
"transactionId": "UyC5P5qKPZaltMmmZAWdakhlDXsBF6qmyrbWYFchRTk",
"ttlSeconds": 3600
},
"alice": {
"transactionId": "kMk95k_3R8x_7d3wB9tEOiL5v6n8QhR_VnFCh3aeE3f",
"ttlSeconds": 900,
"owner": "alice-wallet-address-123...",
"displayName": "Alice's Portfolio",
"logo": "avatar-tx-id-456...",
"description": "Personal portfolio and blog",
"keywords": ["portfolio", "personal", "blog"]
},
"zed": {
"transactionId": "-k7t8xMoB8hW482609Z9F4bTFMC3MnuW8bTvTyT8pFI",
"ttlSeconds": 900
},

"ardrive": {
"transactionId": "-cucucachoodwedwedoiwepodiwpodiwpoidpwoiedp",
"ttlSeconds": 900
Expand Down Expand Up @@ -2329,27 +2367,40 @@ const { id: txId } = await ant.removeController(
);
```

#### `setBaseNameRecord({ transactionId, ttlSeconds })`
#### `setBaseNameRecord({ transactionId, ttlSeconds, owner?, displayName?, logo?, description?, keywords? })`

Adds or updates the base name record for the ANT. This is the top level name of the ANT (e.g. ardrive.ar.io)
Adds or updates the base name record for the ANT. This is the top level name of the ANT (e.g. ardrive.ar.io). Supports undername ownership delegation and metadata.

_Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._

```typescript
// get the ant for the base name
const arnsRecord = await ario.getArNSRecord({ name: 'ardrive' });
const ant = await ANT.init({ processId: arnsName.processId });

// Basic usage
const { id: txId } = await ant.setBaseNameRecord({
transactionId: '432l1cy0aksiL_x9M359faGzM_yjralacHIUo8_nQXM',
ttlSeconds: 3600,
});

// ardrive.ar.io will now resolve to the provided 432l1cy0aksiL_x9M359faGzM_yjralacHIUo8_nQXM transaction id
// With ownership delegation and metadata
const { id: txId } = await ant.setBaseNameRecord({
transactionId: '432l1cy0aksiL_x9M359faGzM_yjralacHIUo8_nQXM',
ttlSeconds: 3600,
owner: 'user-wallet-address-123...', // delegate ownership to another address
displayName: 'ArDrive', // display name
logo: 'logo-tx-id-123...', // logo transaction ID
description: 'Decentralized storage application',
keywords: ['storage', 'decentralized', 'web3'],
});

// ardrive.ar.io will now resolve to the provided transaction id and include metadata
```

#### `setUndernameRecord({ undername, transactionId, ttlSeconds })`
#### `setUndernameRecord({ undername, transactionId, ttlSeconds, owner?, displayName?, logo?, description?, keywords? })`

Adds or updates an undername record for the ANT. An undername is appended to the base name of the ANT (e.g. dapp_ardrive.ar.io)
Adds or updates an undername record for the ANT. An undername is appended to the base name of the ANT (e.g. dapp_ardrive.ar.io). Supports undername ownership delegation and metadata.

_Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._

Expand All @@ -2358,6 +2409,8 @@ _Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._
```typescript
const arnsRecord = await ario.getArNSRecord({ name: 'ardrive' });
const ant = await ANT.init({ processId: arnsName.processId });

// Basic usage
const { id: txId } = await ant.setUndernameRecord(
{
undername: 'dapp',
Expand All @@ -2368,7 +2421,23 @@ const { id: txId } = await ant.setUndernameRecord(
{ tags: [{ name: 'App-Name', value: 'My-Awesome-App' }] },
);

// dapp_ardrive.ar.io will now resolve to the provided 432l1cy0aksiL_x9M359faGzM_yjralacHIUo8_nQXM transaction id
// With ownership delegation and metadata
const { id: txId } = await ant.setUndernameRecord(
{
undername: 'alice',
transactionId: '432l1cy0aksiL_x9M359faGzM_yjralacHIUo8_nQXM',
ttlSeconds: 900,
owner: 'alice-wallet-address-123...', // delegate ownership to Alice
displayName: "Alice's Site", // display name
logo: 'avatar-tx-id-123...', // avatar/logo transaction ID
description: 'Personal portfolio and blog',
keywords: ['portfolio', 'personal', 'blog'],
},
{ tags: [{ name: 'App-Name', value: 'My-Awesome-App' }] },
);

// dapp_ardrive.ar.io will now resolve to the provided transaction id
// alice_ardrive.ar.io will be owned by Alice and include metadata
```

#### `removeUndernameRecord({ undername })`
Expand Down Expand Up @@ -2616,6 +2685,121 @@ console.log(`Failed to reassign names: ${result.failedReassignedNames}`);

**Returns:** `Promise<{ forkedProcessId: string, reassignedNames: Record<string, AoMessageResult>, failedReassignedNames: Record<string, { id?: string; error: Error }> }>`

#### `transferRecord({ undername, recipient })`

Transfers ownership of a specific record (undername) to another address. This enables delegation of control for individual records within an ANT while maintaining the ANT owner's ultimate authority. The current record owner or ANT owner/controllers can transfer ownership.

_Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._

```typescript
const { id: txId } = await ant.transferRecord({
undername: 'alice', // the subdomain/record to transfer
recipient: 'new-owner-address-123...', // address of the new owner
});

// alice_ardrive.ar.io is now owned by the new owner address
// The new owner can update the record but not other records in the ANT
```

**CLI Usage:**

```bash
# Transfer ownership of a record using the CLI
ar.io transfer-record \
--process-id "ANT_PROCESS_ID" \
--undername "alice" \
--recipient "new-owner-address-123..." \
--wallet-file "path/to/wallet.json"
```

### Understanding Record Ownership

ANTs support ownership of undernames:

1. **ANT Owner** - Has full control over the ANT and all records
2. **Controllers** - Can manage records but cannot transfer ANT ownership
3. **Record Owners** - Can only update their specific delegated records

**Record Owner Permissions:**

- ✅ Update their own record's `transactionId`, `ttlSeconds`, and metadata
- ✅ Transfer ownership of their record to another address
- ❌ Modify other records in the ANT
- ❌ Add/remove controllers or transfer ANT ownership

<!-- prettier-ignore-start -->
> [!CAUTION]
> **Important:** When a record owner updates their own record, they **MUST** include their own address in the `owner` field. If the `owner` field is omitted or set to a different address, the record ownership will be transferred or renounced.
<!-- prettier-ignore-end -->

#### Record Owner Workflow Examples

**Checking Record Ownership:**

```typescript
const record = await ant.getRecord({ undername: 'alice' });
console.log(`Record owner: ${record.owner}`);
console.log(`Transaction ID: ${record.transactionId}`);
```

**Record Owner Updating Their Own Record:**

```typescript
// Alice (record owner) updating her own record
const aliceAnt = ANT.init({
processId: 'ANT_PROCESS_ID',
signer: new ArweaveSigner(aliceJwk), // Alice's wallet
});

// ✅ CORRECT: Alice includes her own address as owner
const { id: txId } = await aliceAnt.setUndernameRecord({
undername: 'alice',
transactionId: 'new-content-tx-id-456...',
ttlSeconds: 1800,
owner: 'alice-wallet-address-123...', // MUST be Alice's own address
displayName: 'Alice Updated Portfolio',
description: 'Updated personal portfolio and blog',
});

// ❌ WRONG: Omitting owner field will renounce ownership
const badUpdate = await aliceAnt.setUndernameRecord({
undername: 'alice',
transactionId: 'new-content-tx-id-456...',
ttlSeconds: 1800,
// Missing owner field - this will renounce ownership!
});

// ❌ WRONG: Setting different owner will transfer ownership
const badTransfer = await aliceAnt.setUndernameRecord({
undername: 'alice',
transactionId: 'new-content-tx-id-456...',
ttlSeconds: 1800,
owner: 'someone-else-address-789...', // This transfers ownership to someone else!
});
```

**What Happens When Record Ownership is Renounced:**

If a record owner updates their record without including the `owner` field, the record becomes owned by the ANT owner/controllers again:

```typescript
// Before: alice record is owned by alice-wallet-address-123...
const recordBefore = await ant.getRecord({ undername: 'alice' });
console.log(recordBefore.owner); // "alice-wallet-address-123..."

// Alice updates without owner field
await aliceAnt.setUndernameRecord({
undername: 'alice',
transactionId: 'new-tx-id...',
ttlSeconds: 900,
// No owner field = renounces ownership
});

// After: record ownership reverts to ANT owner
const recordAfter = await ant.getRecord({ undername: 'alice' });
console.log(recordAfter.owner); // undefined (controlled by ANT owner again)
```

### Configuration

ANT clients can be configured to use custom AO process. Refer to [AO Connect] for more information on how to configure the AO process to use specific AO infrastructure.
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ar.io/sdk",
"version": "3.19.0",
"version": "3.20.0-alpha.1",
"repository": {
"type": "git",
"url": "git+https://github.com/ar-io/ar-io-sdk.git"
Expand Down Expand Up @@ -81,7 +81,8 @@
"example:esm": "cd examples/esm && yarn && node index.mjs",
"example:cjs": "yarn build:cjs && yarn link && cd examples/cjs && yarn && node index.cjs",
"example:web": "yarn build:web && http-server --port 8080 --host -o examples/web",
"example:vite": "yarn build:esm && yarn link && cd examples/vite && yarn && yarn start"
"example:vite": "yarn build:esm && yarn link && cd examples/vite && yarn && yarn start",
"cli:local": "node lib/esm/cli/cli.js"
},
"devDependencies": {
"@commitlint/cli": "^17.1.2",
Expand Down
14 changes: 13 additions & 1 deletion src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { version } from '../version.js';
import {
setAntBaseNameCLICommand,
setAntRecordCLICommand,
setAntUndernameCLICommand,
transferRecordOwnershipCLICommand,
upgradeAntCLICommand,
} from './commands/antCommands.js';
import {
Expand Down Expand Up @@ -101,6 +103,7 @@ import {
setAntUndernameOptions,
tokenCostOptions,
transferOptions,
transferRecordOwnershipOptions,
updateGatewaySettingsOptions,
upgradeAntOptions,
vaultedTransferOptions,
Expand Down Expand Up @@ -764,6 +767,7 @@ makeCommand<ANTStateCLIOptions>({
state,
signer: requiredAoSignerFromOptions(options),
logger: getLoggerFromOptions(options),
...(options.module !== undefined ? { module: options.module } : {}),
});

return {
Expand Down Expand Up @@ -895,7 +899,15 @@ makeCommand({
name: 'set-ant-undername',
description: 'Set an undername of an ANT process',
options: setAntUndernameOptions,
action: setAntRecordCLICommand,
action: setAntUndernameCLICommand,
});

makeCommand({
name: 'transfer-record',
description:
'Transfer ownership of a specific record (undername) to another address',
options: transferRecordOwnershipOptions,
action: transferRecordOwnershipCLICommand,
});

makeCommand({
Expand Down
Loading