
Research
/Security News
OpenAPI React Query Codegen Compromised in Mini Shai-Hulud npm Supply Chain Attack
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.
@agirails/sdk
Advanced tools
AGIRAILS SDK for the ACTP (Agent Commerce Transaction Protocol) - Unified mock + blockchain support
The official TypeScript SDK for the Agent Commerce Transaction Protocol (ACTP) - enabling AI agents to transact with each other through blockchain-based escrow.
0x... addresses route to ACTP, https:// to x402, agent IDs to ERC-8004ACTP_PRIVATE_KEY policy, ACTP_KEYSTORE_BASE64 for CI/CD, 30-min TTL cachepending-publish.jsonactp deploy:env (generate base64 keystore) + actp deploy:check (scan for exposed secrets)npm install @agirails/sdk
Or with yarn:
yarn add @agirails/sdk
Get started with real transactions on Base Sepolia testnet:
# Install CLI globally
npm install -g @agirails/sdk
# One-time setup: generates encrypted keystore at .actp/keystore.json
ACTP_KEY_PASSWORD=your-password actp init -m testnet
# Check your balance
ACTP_KEY_PASSWORD=your-password actp balance
# Make a payment
ACTP_KEY_PASSWORD=your-password actp pay 0xProviderAddress 100 --deadline 24h
# Watch transaction status
actp watch TX_ID
Alternative: Set
ACTP_PRIVATE_KEYenv var to use a raw private key instead of the keystore.
The simplest way to make a payment - just specify who, how much, and go:
import { ACTPClient } from '@agirails/sdk';
async function main() {
// Create client in mock mode (no blockchain needed)
const client = await ACTPClient.create({
mode: 'mock',
requesterAddress: '0x1111111111111111111111111111111111111111',
});
// Pay a provider
const result = await client.basic.pay({
to: '0xabcdefABCDEFabcdefABCDEFabcdefABCDEFabcd',
amount: '100.00', // $100 USDC
deadline: '24h', // Optional: expires in 24 hours
});
console.log('Transaction ID:', result.txId);
console.log('State:', result.state);
}
main();
For applications that need explicit control over each transaction step:
import { ACTPClient } from '@agirails/sdk';
import { ethers } from 'ethers';
async function main() {
// No wallet param needed — auto-detects .actp/keystore.json
const client = await ACTPClient.create({
mode: 'testnet', // or 'mainnet' for production
requesterAddress: '0x1111111111111111111111111111111111111111',
});
// Step 1: Create transaction (no funds locked yet)
const txId = await client.standard.createTransaction({
provider: '0xabcdefABCDEFabcdefABCDEFabcdefABCDEFabcd',
amount: '100.50',
deadline: '7d',
disputeWindow: 172800, // 2 days in seconds
});
console.log('Created transaction:', txId);
// Step 2: Link escrow (locks funds, moves to COMMITTED)
const escrowId = await client.standard.linkEscrow(txId);
console.log('Escrow linked:', escrowId);
// Step 3: Provider starts work (REQUIRED before DELIVERED!)
await client.standard.transitionState(txId, 'IN_PROGRESS');
// Step 4: Provider delivers with dispute window proof
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
const disputeWindowProof = abiCoder.encode(['uint256'], [172800]); // 2 days
await client.standard.transitionState(txId, 'DELIVERED', disputeWindowProof);
// Step 5: Release funds to provider (after dispute window)
await client.standard.releaseEscrow(txId);
console.log('Payment complete!');
}
main();
For custom workflows and maximum flexibility:
import { ACTPClient } from '@agirails/sdk';
async function main() {
const client = await ACTPClient.create({
mode: 'mock',
requesterAddress: '0x1111111111111111111111111111111111111111',
});
// Direct runtime access
const runtime = client.advanced;
// Create transaction with full control
const txId = await runtime.createTransaction({
requester: '0x...',
provider: '0x...',
amount: '1000000', // 1.00 USDC (raw wei, 6 decimals)
deadline: 1735689600,
disputeWindow: 86400,
serviceDescription: '0x...'
});
// Get transaction details
const tx = await runtime.getTransaction(txId);
console.log('State:', tx.state, 'Amount:', tx.amount);
}
main();
ACTP transactions follow an 8-state lifecycle:
INITIATED → QUOTED → COMMITTED → IN_PROGRESS → DELIVERED → SETTLED
↘ ↘ ↘
CANCELLED CANCELLED DISPUTED → SETTLED
| State | Description |
|---|---|
INITIATED | Transaction created, no escrow linked |
QUOTED | Provider submitted price quote (optional) |
COMMITTED | Escrow linked, funds locked |
IN_PROGRESS | Provider actively working (optional) |
DELIVERED | Work delivered with proof |
SETTLED | Payment released (terminal) |
DISPUTED | Under dispute resolution |
CANCELLED | Cancelled before completion (terminal) |
// Mock mode - local testing, no blockchain
const client = await ACTPClient.create({
mode: 'mock',
requesterAddress: '0x1111111111111111111111111111111111111111',
stateDirectory: '.actp' // Optional: persist state to disk
});
// Testnet mode - Base Sepolia (auto-detects keystore or ACTP_PRIVATE_KEY)
const client = await ACTPClient.create({
mode: 'testnet',
requesterAddress: '0x1111111111111111111111111111111111111111',
rpcUrl: 'https://sepolia.base.org' // Optional: custom RPC
});
// Mainnet mode - Base (auto-detects keystore or ACTP_PRIVATE_KEY)
const client = await ACTPClient.create({
mode: 'mainnet',
requesterAddress: '0x1111111111111111111111111111111111111111',
});
The SDK accepts amounts in multiple formats:
// All equivalent to $100.50 USDC
await client.basic.pay({ to: '0x...', amount: 100.50 });
await client.basic.pay({ to: '0x...', amount: '100.50' });
await client.basic.pay({ to: '0x...', amount: '$100.50' });
await client.basic.pay({ to: '0x...', amount: '100500000' }); // Wei
// Relative formats
deadline: '1h' // 1 hour from now
deadline: '24h' // 24 hours from now
deadline: '7d' // 7 days from now
// Absolute timestamp
deadline: 1735689600 // Unix timestamp
// ISO date string
deadline: '2027-01-01T00:00:00Z'
The SDK provides structured exceptions with error codes:
import {
ACTPError,
TransactionNotFoundError,
InvalidStateTransitionError,
InsufficientBalanceError,
ValidationError
} from '@agirails/sdk';
try {
await client.basic.pay({ to: 'invalid', amount: 100 });
} catch (error) {
if (error instanceof ValidationError) {
console.log('Validation failed:', error.message);
console.log('Error code:', error.code);
console.log('Details:', error.details);
} else if (error instanceof InsufficientBalanceError) {
console.log(`Need ${error.required}, have ${error.available}`);
} else if (error instanceof ACTPError) {
console.log(`ACTP error [${error.code}]: ${error.message}`);
}
}
ACTPError (base)
├── TransactionNotFoundError
├── InvalidStateTransitionError
├── EscrowNotFoundError
├── InsufficientBalanceError
├── DeadlinePassedError
├── DisputeWindowActiveError
├── ContractPausedError
├── ValidationError
│ ├── InvalidAddressError
│ └── InvalidAmountError
├── NetworkError
│ ├── TransactionRevertedError
│ └── SignatureVerificationError
├── StorageError
│ ├── InvalidCIDError
│ ├── UploadTimeoutError
│ └── ContentNotFoundError
└── AgentLifecycleError
The SDK includes a full-featured CLI for interacting with ACTP:
# Payment operations
actp pay <provider> <amount> [--deadline TIME] [--service TEXT]
actp balance [ADDRESS]
actp mint --amount AMOUNT # Mock mode only
# Transaction management (subcommands of `actp tx`)
actp tx list [--state STATE] [--limit N]
actp tx status <tx_id>
actp tx cancel <tx_id>
# Time manipulation (mock mode only)
actp time advance <seconds>
actp time set <timestamp>
actp time now
# Watch transaction state changes (streams updates)
actp watch <tx_id> [--interval SECONDS] [--format json|text]
# Batch operations from file
actp batch <command_file> [--parallel N] [--continue-on-error]
# Dry-run simulation
actp simulate pay <provider> <amount>
actp simulate fee <amount>
# Publish agent config to on-chain registry
actp publish [--dry-run]
# Pull config from on-chain registry
actp pull [--network base-sepolia]
# Compare local config vs on-chain
actp diff [--network base-sepolia]
# Generate ACTP_KEYSTORE_BASE64 for CI/CD
actp deploy:env
# Scan repo for exposed secrets
actp deploy:check [--fix] [--quiet]
# Initialize keystore (one-time setup)
ACTP_KEY_PASSWORD=your-password actp init -m testnet # or mainnet
ACTP_KEY_PASSWORD=your-password actp init -m mainnet
# General config
actp config set <key> <value>
actp config get <key>
actp config list
actp config reset
# Available config keys:
# network: base-sepolia | base-mainnet | mock
# rpc-url: RPC endpoint URL
# state-directory: Directory for mock state persistence
# Human-readable (default)
actp tx list
# JSON output for scripting
actp tx list --format json
# NDJSON streaming for watch
actp watch TX_ID --format ndjson
Run the test suite:
# Run all tests
npm test
# Run with verbose output
npm test -- --verbose
# Run specific test file
npm test -- src/__tests__/client.test.ts
# Run tests matching pattern
npm test -- --testNamePattern="pay"
# Run with coverage
npm run test:coverage
| Method | Description |
|---|---|
ACTPClient.create(config) | Factory method to create client |
client.basic | Access basic adapter |
client.standard | Access standard adapter |
client.advanced | Access runtime directly |
client.getBalance() | Get USDC balance |
client.reset() | Reset mock state |
| Method | Description |
|---|---|
pay(params) | Create and fund transaction |
checkStatus(txId) | Get transaction status |
getBalance() | Get formatted balance |
| Method | Description |
|---|---|
createTransaction(params) | Create transaction |
linkEscrow(txId) | Link escrow and lock funds |
transitionState(txId, state, proof?) | Transition to new state |
releaseEscrow(txId) | Release funds |
getTransaction(txId) | Get transaction details |
getAllTransactions() | List all transactions |
import { serviceDirectory, request, provide } from '@agirails/sdk';
// serviceDirectory is an in-memory, per-process singleton.
// Providers register automatically when calling provide().
// Find providers for a service
const providers = serviceDirectory.findProviders('text-gen');
// Returns string[] of provider addresses
import { Agent, AgentConfig } from '@agirails/sdk';
// Create an agent (auto-detects keystore, no wallet param needed)
const agent = new Agent({
name: 'my-agent',
network: 'testnet',
});
// Register services via agent.provide()
agent.provide('text-generation', async (job) => {
return { result: `Processed: ${job.input}` };
});
await agent.start();
This TypeScript SDK maintains full parity with the Python SDK:
| Feature | TypeScript SDK | Python SDK |
|---|---|---|
| DeliveryProof Schema | AIP-4 v1.1 (12 fields) | AIP-4 v1.1 (12 fields) |
| Result Hashing | keccak256 | keccak256 |
| JSON Canonicalization | Insertion order | Insertion order |
| EIP-712 Signing | Full support | Full support |
| Level0 API | Full ACTP flow | Full ACTP flow |
| Level1 Agent API | Complete | Complete |
| CLI Commands | watch, batch, simulate, publish, pull, diff, deploy:* | watch, batch, simulate, publish, pull, diff, deploy:* |
| Adapter Routing | ACTP + x402 + ERC-8004 | ACTP + x402 + ERC-8004 |
| ERC-8004 Bridge | Identity + Reputation | Identity + Reputation |
| AGIRAILS.md Config | publish/pull/diff/drift detection | publish/pull/diff |
| Keystore AIP-13 | Full (30-min TTL cache) | Full (30-min TTL cache) |
| Lazy Publish | pending-publish lifecycle | pending-publish lifecycle |
| Nonce Tracking | SecureNonce, ReceivedNonceTracker | SecureNonce, ReceivedNonceTracker |
| Attestation Tracking | UsedAttestationTracker | UsedAttestationTracker |
Shared Test Vectors: Both SDKs use the same JSON test fixtures to ensure identical behavior.
| Network | Chain ID | Status |
|---|---|---|
| Base Sepolia | 84532 | ✅ Active (Testnet) |
| Base Mainnet | 8453 | ✅ Active |
QuoteChannel and RelayChannel route consumer-supplied base URLs through assertSafePeerUrl, rejecting loopback, RFC1918, link-local (incl. cloud metadata 169.254.169.254), and IPv4-mapped IPv6 bypass shapes by default. Opt-in dev escape: allowInsecureTargets: true.How the SDK treats wallet keys and other sensitive material:
What the SDK reads:
ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD — encrypted keystore (preferred for CI / deploy targets). The base64 blob and the password should live in separate secret scopes (different vaults, env groups, or teams) so neither alone is sufficient.ACTP_PRIVATE_KEY — raw hex private key. Testnet only; the SDK refuses this path on mainnet mode and routes you to the keystore pattern instead..actp/keystore.json + ACTP_KEY_PASSWORD — the on-disk file the keystore env vars are derived from.AGIRAILS_PUBLISH_KEY — public client identifier for the publish proxy (same threat model as a Firebase / Stripe publishable key; safe to embed, no privileged scope).What the SDK never reads:
--key, --mnemonic, --secret, or --token flag exists on any actp subcommand. This avoids the ps / shell history / CI-log leakage class (CWE-532, CWE-312).What the SDK logs:
What actp init does for downstream consumers:
.actp/, .env, and .env.* to .gitignore so a forgetful operator can't accidentally commit a populated .env..env.example documenting the keystore + RPC schema with placeholder values only.If a CI / deployment context needs sensitive material, prefer file-based delivery (mounted secrets, encrypted-at-rest stores) over env vars where the platform supports it, and never echo command lines through set -x while ACTP env vars are populated.
All state-changing operations in ACTPKernel wait for block confirmations before events are emitted. The default is 2 confirmations (~4-6s on Base L2's ~2s blocks), configurable via BlockchainRuntimeConfig.confirmations. The SDK's EventMonitor receives already-confirmed events; no additional confirmation handling is needed at the application layer.
AGIRAILS uses did:ethr DIDs based on the ERC-1056 standard for identity management.
Every Ethereum address automatically IS a DID - no registration required:
did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb
↑ ↑
chainId address
import { DIDResolver } from '@agirails/sdk';
// Build DID from address (no registration needed!)
const did = DIDResolver.buildDID('0x742d35cc6634c0532925a3b844bc9e7595f0beb', 84532);
// → 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb'
// Parse DID components
const parsed = DIDResolver.parseDID(did);
console.log(parsed.method); // 'ethr'
console.log(parsed.chainId); // 84532
console.log(parsed.address); // '0x742d35cc6634c0532925a3b844bc9e7595f0beb'
// Validate DID format
const isValid = DIDResolver.isValidDID(did); // true
import { DIDResolver } from '@agirails/sdk';
// Create resolver for Base Sepolia
const resolver = await DIDResolver.create({ network: 'base-sepolia' });
// Resolve DID to W3C DID Document
const result = await resolver.resolve('did:ethr:84532:0x742d35cc...');
if (result.didDocument) {
console.log('Controller:', result.didDocument.controller);
console.log('Verification Methods:', result.didDocument.verificationMethod);
console.log('Service Endpoints:', result.didDocument.service);
}
import { DIDResolver } from '@agirails/sdk';
const resolver = await DIDResolver.create({ network: 'base-sepolia' });
// Verify a signature was made by a DID's controller (or authorized delegate)
const result = await resolver.verifySignature(
'did:ethr:84532:0x742d35cc...', // DID
'Hello AGIRAILS', // Original message
'0x1234...', // Signature
{ chainId: 84532 } // Verification options
);
if (result.valid) {
console.log('Signature valid!');
console.log('Signer:', result.signer);
console.log('Is delegate:', result.isDelegate);
}
For advanced use cases, use DIDManager to manage delegates and attributes:
import { DIDManager } from '@agirails/sdk';
// Create manager with signer
const manager = new DIDManager(signer, { network: 'base-sepolia' });
// Add a signing delegate (valid for 24 hours)
await manager.addDelegate(
'did:ethr:84532:0x742d35cc...', // Your DID
'0xDelegateAddress...', // Delegate address
'sigAuth', // Delegate type
86400 // Validity in seconds
);
// Rotate key ownership
await manager.changeOwner(
'did:ethr:84532:0x742d35cc...',
'0xNewOwnerAddress...'
);
// Add service endpoint attribute
await manager.setAttribute(
'did:ethr:84532:0x742d35cc...',
'did/svc/AgentService',
'https://my-agent.example.com/api',
86400
);
DIDs are used internally for:
# Wallet resolution (checked in order):
ACTP_PRIVATE_KEY=0x... # Raw private key (fail-closed on mainnet — see AIP-13)
ACTP_KEYSTORE_BASE64=... # Base64 keystore for CI/CD (use actp deploy:env to generate)
ACTP_KEY_PASSWORD=... # Decrypts keystore (ACTP_KEYSTORE_BASE64 or .actp/keystore.json)
# Optional
BASE_SEPOLIA_RPC=... # Custom RPC for Base Sepolia
BASE_MAINNET_RPC=... # Custom RPC for Base Mainnet
CDP_API_KEY=... # Coinbase Developer Platform API key
PIMLICO_API_KEY=... # Pimlico bundler/paymaster API key
IPFS_GATEWAY=... # Custom IPFS gateway URL
fetch and AbortController)The SDK ships as CommonJS only. It works with require() and with bundlers (webpack, esbuild, Rollup) that support CJS. If you are using ESM (import syntax), Node.js will auto-interop with CJS modules — no additional configuration is needed.
Apache 2.0 License - see LICENSE for details.
FAQs
AGIRAILS SDK for the ACTP (Agent Commerce Transaction Protocol) - Unified mock + blockchain support
The npm package @agirails/sdk receives a total of 87 weekly downloads. As such, @agirails/sdk popularity was classified as not popular.
We found that @agirails/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.

Research
/Security News
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.

Security News
Socket joins more than 100 technology, cybersecurity, and financial organizations calling for a global surge in cyber defense.

Product
Enterprise security teams can now detect malware, credential theft, suspicious network activity, and risky updates across Microsoft Edge extensions.