SDK Reference
Complete API documentation for:
@agirails/sdk(TypeScript/Node)agirails-sdk(Python)
Make sure you have:
- Node.js 16+ (TS) or Python 3.9+ (PY)
- Private key for Base Sepolia testnet wallet
- ~0.01 ETH for gas fees (get from faucet)
- Mock USDC tokens (see Installation Guide)
- Basic understanding of async/await (TS) or Python coroutines if needed
Estimated time to first transaction: ~5 minutes
Want to jump straight to code? Clone our examples:
git clone https://github.com/agirails/sdk-examples
cd sdk-examples && npm install
npm run example:happy-path
Installation​
- TypeScript
- Python
npm install @agirails/sdk
# or
yarn add @agirails/sdk
# or
pnpm add @agirails/sdk
Requirements:
- Node.js >= 16.0.0
- TypeScript 5.2+ (for TypeScript users)
- ethers.js v6 (included as dependency)
pip install agirails-sdk
Requirements:
- Python 3.9+
- web3.py v6 (installed as dependency)
Quick Reference​
| Task | Method | Description |
|---|---|---|
| Start payment | client.kernel.createTransaction() | Create new transaction |
| Lock funds | client.fundTransaction() | Approve USDC + link escrow |
| Check status | client.kernel.getTransaction() | Get transaction details |
| Progress state | client.kernel.transitionState() | Move to next state |
| Settle payment | client.releaseEscrowWithVerification() | Verify + release funds |
Common Flow: Create -> Fund -> (Provider delivers) -> Release
See Common Patterns for complete workflows.
Architecture​
| Module | Purpose | Key Methods |
|---|---|---|
kernel | Transaction lifecycle | createTransaction, transitionState, releaseEscrow |
escrow | USDC management | approveToken, getEscrowBalance |
eas | Attestations | attestDeliveryProof, verifyDeliveryAttestation |
events | Real-time monitoring | watchTransaction, waitForState |
quote | Price negotiation | build, verify, computeHash |
proofGenerator | Delivery proofs | generateDeliveryProof, hashContent |
messageSigner | EIP-712 signing | signMessage, verifySignature |
Difficulty Levels​
Throughout this reference, methods are marked with difficulty indicators:
| Icon | Level | Description |
|---|---|---|
| 🟢 | Basic | Simple to use, minimal setup required |
| 🟡 | Intermediate | Requires understanding of the protocol flow |
| 🔴 | Advanced | Complex logic, use with caution |
Gas Costs​
Estimated gas costs on Base Sepolia (L2 fees are very low):
| Operation | Gas Units | Cost (USD)* |
|---|---|---|
createTransaction | ~85,000 | ~$0.001 |
fundTransaction | ~120,000 | ~$0.001 |
transitionState | ~45,000 | ~$0.0005 |
anchorAttestation | ~50,000 | ~$0.0005 |
releaseEscrow | ~65,000 | ~$0.0007 |
| Full Happy Path | ~365,000 | ~$0.004 |
*Costs estimated at Base L2 gas prices (~0.001 gwei). Actual costs may vary.
Base is an Ethereum L2 with gas costs 100x cheaper than mainnet. A full transaction lifecycle costs less than $0.01.
ACTPClient​
The main entry point for all SDK operations. Use the async factory method ACTPClient.create() to instantiate.
create()​
🟢 BasicCreates and initializes an ACTPClient instance. This is the recommended way to create a client.
- TypeScript
- Python
static async create(config: ACTPClientConfig): Promise<ACTPClient>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
config.network | 'base-sepolia' | 'base-mainnet' | Yes | Network to connect to |
config.privateKey | string | No* | Private key for signing transactions |
config.signer | Signer | No* | ethers.js Signer instance |
config.provider | JsonRpcProvider | No | Custom ethers.js provider |
config.rpcUrl | string | No | Custom RPC URL (overrides network default) |
config.contracts | object | No | Override contract addresses |
config.contracts.actpKernel | string | No | Custom ACTPKernel address |
config.contracts.escrowVault | string | No | Custom EscrowVault address |
config.contracts.usdc | string | No | Custom USDC token address |
config.gasSettings | object | No | Gas price settings |
config.gasSettings.maxFeePerGas | bigint | No | Maximum fee per gas |
config.gasSettings.maxPriorityFeePerGas | bigint | No | Maximum priority fee |
config.eas | EASConfig | No | EAS configuration for attestations |
*Either privateKey or signer must be provided.
Returns​
Promise<ACTPClient> - Initialized client ready for use
Throws​
ValidationError- If configuration is invalidNetworkError- If unable to connect to network
Example​
import { ACTPClient } from '@agirails/sdk';
// Basic setup with private key
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!
});
// With custom RPC and gas settings
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!,
rpcUrl: 'https://base-sepolia.g.alchemy.com/v2/YOUR_KEY',
gasSettings: {
maxFeePerGas: 2000000000n, // 2 gwei
maxPriorityFeePerGas: 1000000000n // 1 gwei
}
});
// With EAS attestation support
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!,
eas: {
contractAddress: '0x4200000000000000000000000000000000000021',
deliveryProofSchemaId: '0x1b0ebdf0...'
}
});
from agirails_sdk import ACTPClient, Network
# Basic setup with private key
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
)
# With custom RPC and gas settings (tx_overrides)
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
rpc_url="https://base-sepolia.g.alchemy.com/v2/YOUR_KEY",
tx_overrides={
"maxFeePerGas": 2_000_000_000, # 2 gwei
"maxPriorityFeePerGas": 1_000_000_000, # 1 gwei
},
)
# With EAS attestation support (delivery proofs)
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
)
# EAS contract/schema are preconfigured for Base Sepolia; pass rpc_url if overriding network config.
Parameters (Python)​
| Name | Type | Required | Description |
|---|---|---|---|
network | Network | Yes | Network to connect to (BASE_SEPOLIA, BASE) |
private_key | str | Yes | Private key for signing transactions |
rpc_url | str | No | Custom RPC URL |
tx_overrides | dict | No | Gas/nonce overrides (maxFeePerGas, maxPriorityFeePerGas, nonce) |
manual_nonce | bool | No | Enable manual nonce management |
Returns​
ACTPClient - Initialized client ready for use
Throws​
ValidationError- If configuration is invalidRpcError- If unable to connect or send transaction
getAddress()​
🟢 BasicReturns the Ethereum address of the connected signer.
- TypeScript
- Python
async getAddress(): Promise<string>
Returns​
Promise<string> - Ethereum address (checksummed)
Example​
const address = await client.getAddress();
console.log('Connected as:', address);
// Output: Connected as: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
address = client.address
print("Connected as:", address)
# Output: Connected as: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
getNetworkConfig() 🟢​
Returns the current network configuration.
- TypeScript
- Python
getNetworkConfig(): NetworkConfig
Returns​
NetworkConfig - Network configuration object
interface NetworkConfig {
name: string;
chainId: number;
rpcUrl: string;
blockExplorer: string;
contracts: {
actpKernel: string;
escrowVault: string;
usdc: string;
eas: string;
easSchemaRegistry: string;
};
eas: {
deliverySchemaUID: string;
};
gasSettings: {
maxFeePerGas: bigint;
maxPriorityFeePerGas: bigint;
};
}
Example​
const config = client.getNetworkConfig();
console.log('Network:', config.name); // "Base Sepolia"
console.log('Chain ID:', config.chainId); // 84532
console.log('Kernel:', config.contracts.actpKernel);
config = client.config
print("Network:", config.name.value) # "base-sepolia"
print("Chain ID:", config.chain_id) # 84532
print("Kernel:", config.actp_kernel)
Returns​
NetworkConfig - Network configuration dataclass (name, chain_id, rpc_url, actp_kernel, escrow_vault, usdc, eas, eas_schema_registry, delivery_schema_uid, agent_registry)
getProvider() 🟢​
Returns the underlying ethers.js provider.
- TypeScript
- Python
getProvider(): JsonRpcProvider
Returns​
JsonRpcProvider - ethers.js JSON-RPC provider
Example​
const provider = client.getProvider();
const blockNumber = await provider.getBlockNumber();
console.log('Current block:', blockNumber);
provider = client.w3 # Web3 instance
block_number = provider.eth.block_number
print("Current block:", block_number)
Returns​
Web3 - web3.py HTTP provider configured for the selected network
getBlockNumber() 🟢​
Returns the current block number.
- TypeScript
- Python
async getBlockNumber(): Promise<number>
Returns​
Promise<number> - Current block number
Throws​
NetworkError- If unable to fetch block number
Example​
const blockNumber = await client.getBlockNumber();
console.log('Block:', blockNumber);
block_number = client.w3.eth.block_number
print("Block:", block_number)
Returns​
int - Current block number
getGasPrice()​
Returns the current gas price.
- TypeScript
- Python
async getGasPrice(): Promise<bigint>
Returns​
Promise<bigint> - Gas price in wei
Throws​
NetworkError- If unable to fetch gas price
Example​
const gasPrice = await client.getGasPrice();
console.log('Gas price:', gasPrice.toString(), 'wei');
gas_price = client.w3.eth.gas_price
print("Gas price:", gas_price, "wei")
Returns​
int - Gas price in wei
fundTransaction()​
🟢 BasicConvenience method that approves USDC and links escrow in one call. This is the recommended way to fund a transaction.
- TypeScript
- Python
async fundTransaction(txId: string): Promise<string>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID (bytes32 hex string) |
Returns​
Promise<string> - Escrow ID created (bytes32 hex string)
Throws​
ValidationError- If transaction not found, already funded, or deadline passedTransactionRevertedError- If on-chain operation fails
Example​
import { parseUnits } from 'ethers';
// Create transaction first
const txId = await client.kernel.createTransaction({
requester: await client.getAddress(),
provider: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: parseUnits('100', 6),
deadline: Math.floor(Date.now() / 1000) + 86400,
disputeWindow: 7200
});
// Fund the transaction (approves USDC + links escrow)
const escrowId = await client.fundTransaction(txId);
console.log('Escrow ID:', escrowId);
// Transaction is now COMMITTED and ready for provider to work
# Create transaction first
tx_id = client.create_transaction(
requester=client.address,
provider="0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
amount=100_000_000, # 100 USDC
deadline=client.now() + 86400,
dispute_window=7200,
service_hash="0x" + "00" * 32,
)
# Fund the transaction (approves USDC + links escrow)
escrow_id = client.fund_transaction(tx_id)
print("Escrow ID:", escrow_id)
# Transaction is now COMMITTED and ready for provider to work
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
tx_id | str | Yes | Transaction ID (bytes32 hex string) |
Returns​
str - Escrow ID created (bytes32 hex string)
Throws​
ValidationError- Invalid tx/state/deadlineTransactionError/RpcError- On-chain failures
Notes​
- Validates that transaction exists and is in INITIATED or QUOTED state
- Validates deadline has not passed
- Approves exact USDC amount (platform fee deducted on release)
- Auto-generates unique escrow ID
- Auto-transitions transaction to COMMITTED state
See Also​
- createTransaction() - Create transaction before funding
- escrow.approveToken() - Manual USDC approval (if needed)
- kernel.linkEscrow() - Manual escrow linking (advanced)
releaseEscrowWithVerification()​
🟡 IntermediateReleases escrow with EAS attestation verification. This is the secure way to settle transactions.
- TypeScript
- Python
async releaseEscrowWithVerification(
txId: string,
attestationUID: string
): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID to settle |
attestationUID | string | Yes | EAS attestation UID to verify |
Throws​
Error- If EAS is not configuredError- If attestation verification fails (revoked, expired, or txId mismatch)TransactionRevertedError- If escrow release fails
Example​
// Client must be initialized with EAS config
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!,
eas: {
contractAddress: '0x4200000000000000000000000000000000000021',
deliveryProofSchemaId: '0x1b0ebdf0bd20c28ec9d5362571ce8715a55f46e81c3de2f9b0d8e1b95fb5ffce'
}
});
// Get transaction to find attestation UID
const tx = await client.kernel.getTransaction(txId);
// Verify and release in one secure call
await client.releaseEscrowWithVerification(txId, tx.attestationUID);
# Client must be initialized with EAS config (preconfigured for Base Sepolia)
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
)
# Verify and release in one secure call
client.release_escrow_with_verification(tx_id, attestation_uid)
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
tx_id | str | Yes | Transaction ID to settle |
attestation_uid | str | Yes | EAS attestation UID to verify |
Throws​
ValidationError/TransactionError- On invalid inputs or failed verificationRpcError- If transaction fails on-chain
Security Notes​
- Verifies attestation schema matches canonical delivery proof schema
- Verifies attestation is not revoked
- Verifies attestation is not expired
- Verifies attestation txId matches the transaction being settled
- Protects against malicious providers submitting attestations from other transactions
See Also​
- eas.verifyDeliveryAttestation() - Manual verification
- kernel.releaseEscrow() - Release without verification (unsafe)
- kernel.getTransaction() - Get attestation UID from transaction
client.kernel​
The kernel module handles ACTP transaction lifecycle management.
createTransaction()​
🟢 BasicCreates a new ACTP transaction between a requester and provider.
- TypeScript
- Python
async createTransaction(params: CreateTransactionParams): Promise<string>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
params.requester | string | Yes | Address of the requester (payer) |
params.provider | string | Yes | Address of the provider (payee) |
params.amount | bigint | Yes | Amount in USDC (6 decimals). Use parseUnits('10', 6) for 10 USDC |
params.deadline | number | Yes | Unix timestamp when transaction expires |
params.disputeWindow | number | Yes | Seconds for dispute window after delivery |
params.metadata | string | No | Optional bytes32 service hash/quote hash (stored as serviceHash) |
Returns​
Promise<string> - Transaction ID (bytes32 hex string)
Throws​
ValidationError- If parameters are invalid (address format, amount ≤ 0, deadline in past)TransactionRevertedError- If on-chain transaction fails
Example​
import { ACTPClient } from '@agirails/sdk';
import { parseUnits } from 'ethers';
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!
});
const txId = await client.kernel.createTransaction({
requester: await client.getAddress(),
provider: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: parseUnits('100', 6), // 100 USDC
deadline: Math.floor(Date.now() / 1000) + 86400, // 24 hours
disputeWindow: 7200 // 2 hours
});
console.log('Transaction ID:', txId);
// Output: Transaction ID: 0x1234...abcd
from agirails_sdk import ACTPClient, Network
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
)
tx_id = client.create_transaction(
requester=client.address,
provider="0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
amount=100_000_000, # 100 USDC (6 decimals)
deadline=client.now() + 86400, # 24 hours
dispute_window=7200, # 2 hours
service_hash="0x" + "00" * 32,
)
print("Transaction ID:", tx_id)
# Output: Transaction ID: 0x1234...abcd
Parameters (Python)​
| Name | Type | Required | Description |
|---|---|---|---|
requester | str | Yes | Requester address (payer, must match signer) |
provider | str | Yes | Provider address (payee) |
amount | int | Yes | Amount in USDC base units (6 decimals) |
deadline | int | Yes | Unix timestamp expiry |
dispute_window | int | Yes | Seconds for dispute window |
service_hash | str | No | Optional bytes32 hash |
Returns​
str - Transaction ID (bytes32 hex)
Throws​
ValidationError- Invalid params (amount, deadline, addresses)DeadlineError- If deadline is not in futureTransactionError/RpcError- On-chain failure
Notes​
- Transaction starts in
INITIATEDstate - Requester must have sufficient USDC balance to fund later
- Platform minimum is $0.05 USDC (50000 base units)
- Waits for 2 block confirmations for Base L2 reorg safety
See Also​
- fundTransaction() - Fund the transaction after creation
- getTransaction() - Check transaction status
- transitionState() - Progress the transaction state
getTransaction()​
🟢 BasicRetrieves transaction details by ID.
- TypeScript
- Python
async getTransaction(txId: string): Promise<Transaction>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID (bytes32 hex string) |
Returns​
interface Transaction {
txId: string; // Transaction ID
requester: string; // Requester address
provider: string; // Provider address
amount: bigint; // Amount in USDC (6 decimals)
state: State; // Current state (0-7)
createdAt: number; // Creation timestamp
updatedAt: number; // Last update timestamp
deadline: number; // Deadline timestamp
disputeWindow: number; // Dispute window in seconds
escrowContract: string; // Linked escrow contract address
escrowId: string; // Escrow ID
serviceHash: string; // Service hash (quote hash if QUOTED)
attestationUID: string; // EAS attestation UID (delivery proof)
metadata: string; // Optional metadata (quote hash for QUOTED)
platformFeeBpsLocked: number; // Platform fee bps locked at creation
}
Throws​
TransactionNotFoundError- If transaction does not exist
Example​
const tx = await client.kernel.getTransaction(txId);
console.log('State:', State[tx.state]); // "COMMITTED"
console.log('Amount:', tx.amount.toString()); // "100000000"
console.log('Provider:', tx.provider);
console.log('Deadline:', new Date(tx.deadline * 1000));
tx = client.get_transaction(tx_id)
print("State:", tx.state.name) # "COMMITTED"
print("Amount:", tx.amount) # 100000000
print("Provider:", tx.provider)
print("Deadline:", tx.deadline) # Unix timestamp
Returns​
TransactionView dataclass (transaction_id, requester, provider, state, amount, created_at, updated_at, deadline, service_hash, escrow_contract, escrow_id, attestation_uid, dispute_window, metadata, platform_fee_bps_locked)
Throws​
TransactionError/RpcError- If transaction not found or RPC fails
transitionState()​
🟡 IntermediateTransitions a transaction to a new state.
- TypeScript
- Python
async transitionState(
txId: string,
newState: State,
proof?: BytesLike
): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID |
newState | State | Yes | Target state (from State enum) |
proof | BytesLike | No | Optional proof data (for DELIVERED, DISPUTED states) |
Throws​
InvalidStateTransitionError- If transition is not allowed from current stateTransactionRevertedError- If on-chain transaction fails
Example​
import { State } from '@agirails/sdk';
// Provider marks work as in progress
await client.kernel.transitionState(txId, State.IN_PROGRESS);
// Provider delivers result
await client.kernel.transitionState(txId, State.DELIVERED, proofData);
from agirails_sdk import State
# Provider marks work as in progress
client.transition_state(tx_id, State.IN_PROGRESS)
# Provider delivers result
client.transition_state(tx_id, State.DELIVERED, proof_bytes_or_hex)
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
tx_id | str | Yes | Transaction ID |
new_state | State | Yes | Target state |
proof | Union[str, bytes] | No | Optional proof data for DELIVERED/DISPUTED |
Throws​
InvalidStateTransitionError- If transition not allowedTransactionError/RpcError- On-chain failure
State Machine​
See Also​
- State enum - All possible states
- events.waitForState() - Wait for specific state
- events.watchTransaction() - Monitor state changes
submitQuote()​
This method is not implemented in the current V1 contract. The QUOTED state exists but quotes are submitted via transitionState(txId, State.QUOTED, quoteProof) by the provider. A dedicated submitQuote() helper is planned for V2.
To transition to QUOTED state in V1, use:
// V1: Use transitionState to move to QUOTED
const quoteProof = ethers.id('quote-details-hash');
await providerClient.kernel.transitionState(txId, State.QUOTED, quoteProof);
linkEscrow()​
🟡 IntermediateLinks an escrow to a transaction, transitioning it to COMMITTED state.
- TypeScript
- Python
async linkEscrow(
txId: string,
escrowContract: string,
escrowId: string
): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID |
escrowContract | string | Yes | EscrowVault contract address |
escrowId | string | Yes | Unique escrow identifier (bytes32) |
Throws​
ValidationError- If parameters are invalidTransactionRevertedError- If insufficient USDC approval or other contract error
Example​
import { id } from 'ethers';
// First approve USDC
await client.escrow.approveToken(usdcAddress, amount);
// Generate unique escrow ID
const escrowId = id(`escrow-${txId}-${Date.now()}`);
// Link escrow (auto-transitions to COMMITTED)
await client.kernel.linkEscrow(txId, escrowVaultAddress, escrowId);
import secrets
# First approve USDC
client.usdc.functions.approve(
client.config.escrow_vault,
100_000_000, # amount in USDC (6 decimals)
).transact({"from": client.address})
# Generate unique escrow ID
escrow_id = secrets.token_hex(32)
# Link escrow (auto-transitions to COMMITTED)
client.link_escrow(tx_id, escrow_contract=client.config.escrow_vault, escrow_id=escrow_id)
Parameters (Python)​
| Name | Type | Required | Description |
|---|---|---|---|
tx_id | str | Yes | Transaction ID |
escrow_contract | str | Yes | EscrowVault contract address |
escrow_id | str | Yes | Unique escrow identifier (bytes32 hex) |
Throws​
ValidationError- If parameters invalidTransactionError/RpcError- If approval/link fails on-chain
Notes​
- Consumer must approve USDC to EscrowVault BEFORE calling linkEscrow
- Auto-transitions transaction from INITIATED/QUOTED to COMMITTED
- Use
client.fundTransaction()for a simpler one-call approach
releaseMilestone()​
Releases a partial milestone payment.
- TypeScript
- Python
async releaseMilestone(txId: string, amount: bigint): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID |
amount | bigint | Yes | Amount to release in USDC |
The contract takes only (transactionId, amount) - there is no milestone ID parameter. Multiple partial releases are tracked by cumulative amount released, not by milestone index.
Throws​
ValidationError- If amount invalid or exceeds remaining escrowTransactionRevertedError- If contract reverts
Example​
// Release partial payment of 25 USDC
await client.kernel.releaseMilestone(txId, parseUnits('25', 6));
// Release another 25 USDC later
await client.kernel.releaseMilestone(txId, parseUnits('25', 6));
# Release partial payment of 25 USDC
client.release_milestone(tx_id, 25_000_000)
# Release another 25 USDC later
client.release_milestone(tx_id, 25_000_000)
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
tx_id | str | Yes | Transaction ID |
amount | int | Yes | Amount to release in USDC base units (6 decimals) |
Throws​
ValidationError- If amount invalid or exceeds remaining escrowTransactionError/RpcError- If contract reverts
releaseEscrow()​
Releases full escrow to settle the transaction.
- TypeScript
- Python
Security Warning​
V1 contracts do not validate attestation UIDs on-chain. Use client.releaseEscrowWithVerification() instead for secure settlement with attestation verification.
raiseDispute()​
The contract does not have a dedicated raiseDispute() function. Disputes are raised via transitionState() to the DISPUTED state.
V1 Implementation: Use transitionState to move to DISPUTED:
// Requester raises dispute (must be in DELIVERED state, within dispute window)
await requesterClient.kernel.transitionState(txId, State.DISPUTED, '0x');
- Transaction must be in DELIVERED state
- Requester calls
transitionState(txId, DISPUTED, proof)within dispute window - Admin resolves via
resolveDispute()(admin-only function) - Funds distributed per admin decision
deliver() (Convenience)​
Provider helper to mark work as delivered with optional dispute window override.
- TypeScript
- Python
Not available as a convenience helper in TS; use transitionState(txId, State.DELIVERED, proof) directly.
dispute() (Convenience)​
Raise a dispute on a transaction.
- TypeScript
- Python
Not available as a convenience helper in TS; use transitionState(txId, State.DISPUTED, proof) during dispute window.
cancel() (Convenience)​
Cancel a transaction.
- TypeScript
- Python
Not available as a convenience helper in TS; use transitionState(txId, State.CANCELLED, proof) respecting cancellation rules.
resolveDispute()​
This function exists in the contract but can only be called by admin/pauser, not by SDK users. It is used to resolve disputed transactions.
V1 Contract Signature:
function resolveDispute(
bytes32 transactionId,
uint256 requesterAmount,
uint256 providerAmount,
uint256 mediatorAmount,
address mediator
) external onlyRole(PAUSER_ROLE)
Regular users cannot call this method. Contact protocol admin to resolve disputes in V1.
- TypeScript
- Python
// Admin-only
await client.kernel.resolveDispute(txId, {
requesterAmount: parseUnits('2.5', 6),
providerAmount: parseUnits('7', 6),
mediatorAmount: parseUnits('0.5', 6),
mediator: await client.getAddress()
});
client.resolve_dispute(
tx_id,
requester_amount=250_000, # example split
provider_amount=700_000,
mediator_amount=50_000,
mediator=client.address,
)
Throws​
InvalidStateTransitionError- If transaction not in DISPUTEDValidationError- Invalid amounts/mediatorTransactionError/RpcError- On-chain failure
anchorAttestation()​
Anchors an EAS attestation UID to a transaction.
- TypeScript
- Python
async anchorAttestation(
txId: string,
attestationUID: string
): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID |
attestationUID | string | Yes | EAS attestation UID (bytes32) |
Throws​
ValidationError- If attestationUID format is invalidTransactionRevertedError- If contract reverts
Example​
// After creating EAS attestation
const attestation = await client.eas!.attestDeliveryProof(proof, recipient);
await client.kernel.anchorAttestation(txId, attestation.uid);
# After creating EAS attestation (if using EAS client separately)
client.anchor_attestation(tx_id, attestation_uid)
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
tx_id | str | Yes | Transaction ID |
attestation_uid | str | Yes | EAS attestation UID (bytes32 hex) |
Throws​
ValidationError- If UID format invalidTransactionError/RpcError- If contract reverts
getEconomicParams()​
The contract does not have a getEconomicParams() function. Economic parameters are exposed as individual public variables.
V1 Alternative: Query individual contract variables directly:
- TypeScript
- Python
// V1: Read individual public variables from contract
const kernel = client.kernel.getContract(); // Get ethers Contract instance
const platformFeeBps = await kernel.platformFeeBps(); // 100 = 1%
const feeRecipient = await kernel.feeRecipient();
const requesterPenaltyBps = await kernel.requesterPenaltyBps(); // 500 = 5%
// Calculate fee percentage
const feePercent = Number(platformFeeBps) / 100;
console.log('Platform fee:', feePercent + '%'); // "Platform fee: 1%"
kernel = client.kernel # web3.py Contract
platform_fee_bps = kernel.functions.platformFeeBps().call()
fee_recipient = kernel.functions.feeRecipient().call()
requester_penalty_bps = kernel.functions.requesterPenaltyBps().call()
fee_percent = platform_fee_bps / 100
print(f"Platform fee: {fee_percent}%") # "Platform fee: 1%"
Available public variables in V1:
platformFeeBps- Platform fee in basis points (100 = 1%)feeRecipient- Address receiving platform feesrequesterPenaltyBps- Cancellation penalty (500 = 5%)getPendingEconomicParams()- View scheduled parameter changes
estimateCreateTransaction()​
Estimates gas for transaction creation.
- TypeScript
- Python
async estimateCreateTransaction(
params: CreateTransactionParams
): Promise<bigint>
Returns​
Promise<bigint> - Estimated gas units
Example​
const gas = await client.kernel.estimateCreateTransaction({
requester: await client.getAddress(),
provider: '0x742d35...',
amount: parseUnits('100', 6),
deadline: Math.floor(Date.now() / 1000) + 86400,
disputeWindow: 7200
});
console.log('Estimated gas:', gas.toString());
gas = client.estimate_create_transaction(
requester=client.address,
provider="0x742d35...",
amount=100_000_000,
deadline=client.now() + 86400,
dispute_window=7200,
service_hash="0x" + "00" * 32,
)
print("Estimated gas:", gas)
Returns​
int - Estimated gas units
getAddress()​
Returns the ACTPKernel contract address.
- TypeScript
- Python
client.escrow​
The escrow module handles USDC token approvals and escrow state queries.
approveToken()​
Approves USDC tokens for escrow creation.
- TypeScript
- Python
async approveToken(tokenAddress: string, amount: bigint): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
tokenAddress | string | Yes | USDC contract address |
amount | bigint | Yes | Amount to approve (6 decimals) |
Throws​
ValidationError- If address or amount invalidTransactionRevertedError- If approval fails
Example​
const config = client.getNetworkConfig();
const amount = parseUnits('100', 6);
await client.escrow.approveToken(config.contracts.usdc, amount);
config = client.config
amount = 100_000_000 # 100 USDC
tx_hash = client.usdc.functions.approve(
config.escrow_vault,
amount,
).transact({"from": client.address})
client.w3.eth.wait_for_transaction_receipt(tx_hash)
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
token_address | str | Yes | USDC contract address |
amount | int | Yes | Amount to approve (6 decimals) |
Throws​
ValidationError- If address or amount invalidRpcError/TransactionError- If approval fails
Notes​
- Must be called BEFORE
linkEscrow() - Handles USDC's approval reset pattern (sets to 0 first if residual allowance)
- Skips approval if current allowance is sufficient
getEscrow()​
Retrieves escrow details by ID.
- TypeScript
- Python
async getEscrow(escrowId: string): Promise<Escrow>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
escrowId | string | Yes | Escrow ID (bytes32) |
Returns​
interface Escrow {
escrowId: string; // Escrow identifier
kernel: string; // Linked kernel address
txId: string; // Linked transaction ID
token: string; // Token address (USDC)
amount: bigint; // Locked amount
beneficiary: string; // Provider address
createdAt: number; // Creation timestamp
released: boolean; // Whether funds released
}
Example​
const escrow = await client.escrow.getEscrow(escrowId);
console.log('Amount locked:', escrow.amount.toString());
console.log('Released:', escrow.released);
is_active, escrow_amount = client.get_escrow_status(
None,
escrow_id,
expected_requester=None,
expected_provider=None,
expected_amount=None,
)
print("Is active:", is_active)
print("Amount locked:", escrow_amount)
Returns​
Tuple[bool, int] - (is_active, amount); detailed escrow struct is not exposed on-chain (mapping is private), use events for full details
getEscrowBalance()​
Gets the locked balance of an escrow.
- TypeScript
- Python
async getEscrowBalance(escrowId: string): Promise<bigint>
Returns​
Promise<bigint> - Locked amount in USDC
is_active, escrow_amount = client.get_escrow_status(
None,
escrow_id,
expected_requester=None,
expected_provider=None,
expected_amount=None,
)
print("Escrow balance:", escrow_amount)
Returns​
int - Locked amount in USDC base units (6 decimals)
releaseEscrow()​
Releases escrow to specified recipients (only callable by authorized kernel).
- TypeScript
- Python
async releaseEscrow(
escrowId: string,
recipients: string[],
amounts: bigint[]
): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
escrowId | string | Yes | Escrow ID |
recipients | string[] | Yes | Array of recipient addresses |
amounts | bigint[] | Yes | Array of amounts (must match recipients length) |
Throws​
ValidationError- If arrays length mismatch or invalid valuesTransactionRevertedError- If not authorized or other error
client.escrow_release(
escrow_id,
recipients=["0xProvider", "0xPlatform"],
amounts=[99_000_000, 1_000_000],
)
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
escrow_id | str | Yes | Escrow ID |
recipients | list[str] | Yes | Array of recipient addresses |
amounts | list[int] | Yes | Amounts (6 decimals), length must match recipients |
Throws​
ValidationError- If arrays mismatch or invalid valuesTransactionError/RpcError- If not authorized or other error
getTokenBalance()​
Gets USDC balance of an address.
- TypeScript
- Python
async getTokenBalance(
tokenAddress: string,
account: string
): Promise<bigint>
Returns​
Promise<bigint> - Token balance
Example​
const balance = await client.escrow.getTokenBalance(
config.contracts.usdc,
await client.getAddress()
);
console.log('USDC balance:', balance.toString());
balance = client.usdc.functions.balanceOf(client.address).call()
print("USDC balance:", balance)
Returns​
int - Token balance (base units, 6 decimals)
getTokenAllowance()​
Gets USDC allowance for a spender.
- TypeScript
- Python
async getTokenAllowance(
tokenAddress: string,
owner: string,
spender: string
): Promise<bigint>
Returns​
Promise<bigint> - Approved allowance
Example​
const allowance = await client.escrow.getTokenAllowance(
config.contracts.usdc,
await client.getAddress(),
config.contracts.escrowVault
);
console.log('Approved:', allowance.toString());
allowance = client.usdc.functions.allowance(
client.address,
client.config.escrow_vault,
).call()
print("Approved:", allowance)
Returns​
int - Approved allowance
getAddress()​
Returns the EscrowVault contract address.
- TypeScript
- Python
getAddress(): string
escrow_address = client.config.escrow_vault
print(escrow_address)
client.eas​
The EAS module handles Ethereum Attestation Service operations. Only available if configured during client creation.
attestDeliveryProof()​
🟡 IntermediateCreates an EAS attestation for a delivery proof.
- TypeScript
- Python
async attestDeliveryProof(
proof: DeliveryProof,
recipient: string,
options?: { expirationTime?: number; revocable?: boolean }
): Promise<AttestationResponse>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
proof | DeliveryProof | Yes | Delivery proof object |
recipient | string | Yes | Attestation recipient (usually provider) |
options.expirationTime | number | No | Expiration timestamp (0 = never) |
options.revocable | boolean | No | Whether attestation can be revoked (default: true) |
Returns​
interface AttestationResponse {
uid: string; // Attestation UID (bytes32)
transactionHash: string; // On-chain transaction hash
}
Example​
const proof = client.proofGenerator.generateDeliveryProof({
txId,
deliverable: 'Completed analysis report...',
deliveryUrl: 'ipfs://Qm...',
metadata: { mimeType: 'application/json' }
});
const attestation = await client.eas!.attestDeliveryProof(
proof,
providerAddress,
{ revocable: true }
);
console.log('Attestation UID:', attestation.uid);
# Python SDK does not wrap EAS attestation creation.
# Use the EAS contract via web3.py if you need to create attestations,
# then anchor the UID on-chain with client.anchor_attestation(tx_id, uid).
revokeAttestation()​
Revokes a previously created attestation.
- TypeScript
- Python
getAttestation()​
Fetches attestation data from EAS contract.
- TypeScript
- Python
async getAttestation(uid: string): Promise<Attestation>
Returns​
Attestation object with uid, schema, recipient, attester, time, expirationTime, revocable, refUID, data, bump fields.
Python SDK does not expose get_attestation; use EAS contracts directly if needed.
verifyDeliveryAttestation()​
Verifies that an attestation is valid for a specific transaction.
- TypeScript
- Python
async verifyDeliveryAttestation(
txId: string,
attestationUID: string
): Promise<boolean>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Expected transaction ID |
attestationUID | string | Yes | Attestation UID to verify |
Returns​
Promise<boolean> - true if valid
Throws​
Error- If attestation not found, revoked, expired, schema mismatch, or txId mismatch
Example​
try {
await client.eas!.verifyDeliveryAttestation(txId, attestationUID);
console.log('Attestation verified!');
} catch (error) {
console.error('Verification failed:', error.message);
}
attestation = client.verify_delivery_attestation(tx_id, attestation_uid)
print("Attestation UID:", attestation[0].hex()) # tuple[uid, schema, refUID, time, ...]
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
tx_id | str | Yes | Expected transaction ID |
attestation_uid | str | Yes | Attestation UID to verify |
Returns​
tuple - Attestation tuple from EAS (uid, schema, refUID, time, expirationTime, revocationTime, recipient, attester, data)
Throws​
ValidationError- If UID format invalidTransactionError/RpcError- If attestation not found, revoked, expired, schema mismatch, or txId mismatch
client.agentRegistry​
Agent Registry helpers (AIP-7) for registering and managing agent metadata.
- TypeScript
- Python
// Initialized automatically when network config includes agentRegistry
const registry = client.registry!;
const serviceType = 'text-generation';
const serviceTypeHash = registry.computeServiceTypeHash(serviceType);
await registry.registerAgent({
endpoint: 'https://agent.example.com/webhook',
serviceDescriptors: [{
serviceType,
serviceTypeHash,
schemaURI: 'ipfs://QmSchema...',
minPrice: 1_000_000n, // 1 USDC
maxPrice: 50_000_000n, // 50 USDC
avgCompletionTime: 120, // seconds
metadataCID: 'QmMetadata...'
}]
});
// Pagination-aware query (AIP-7 signature)
const agents = await registry.queryAgentsByService({
serviceTypeHash,
minReputation: 5000,
offset: 0,
limit: 50
});
// DID helpers (chain-bound, lowercase)
const did = await registry.buildDID(await client.getAddress()); // did:ethr:<chainId>:<addr>
const profile = await registry.getAgentByDID(did);
Methods (TypeScript)​
registerAgent(params: { endpoint, serviceDescriptors[] })updateEndpoint(newEndpoint: string)addServiceType(serviceType: string)removeServiceType(serviceTypeHash: string)setActiveStatus(isActive: boolean)getAgent(address: string)getAgentByDID(did: string)getServiceDescriptors(address: string)queryAgentsByService({ serviceTypeHash, minReputation, offset, limit })supportsService(address: string, serviceTypeHash: string)computeServiceTypeHash(serviceType: string)buildDID(address: string)
# Compute required hash (lowercase service type)
service_type = "text-generation"
service_type_hash = client.compute_service_type_hash(service_type)
# (Optional) validate descriptors client-side before sending on-chain
descriptors = [{
"serviceType": service_type,
"serviceTypeHash": service_type_hash,
"schemaURI": "ipfs://QmSchema...",
"minPrice": 1_000_000, # 1 USDC
"maxPrice": 50_000_000, # 50 USDC
"avgCompletionTime": 120, # seconds
"metadataCID": "QmMetadata..."
}]
client.validate_service_descriptors(descriptors)
# Register agent (AIP-7)
client.register_agent(
endpoint="https://agent.example.com/webhook",
service_descriptors=descriptors,
)
# Query with pagination (AIP-7 signature)
agents = client.query_agents_by_service(
service_type_hash=service_type_hash,
min_reputation=5000,
offset=0,
limit=50,
)
# DID helpers (chain-bound, lowercase)
did = client.build_did(client.address) # did:ethr:<chainId>:<addr>
profile = client.get_agent_by_did(did)
# Other helpers
client.update_endpoint("https://agent.example.com/new-webhook")
client.add_service_type("compute")
client.remove_service_type("0x" + "ab"*32)
client.set_active_status(True)
services = client.get_service_descriptors("0xAgentAddress")
Methods (Python)​
register_agent(endpoint: str, service_descriptors: list[dict])update_endpoint(new_endpoint: str)add_service_type(service_type: str)remove_service_type(service_type_hash: Union[str, bytes])set_active_status(is_active: bool)get_agent(agent_address: str)get_agent_by_did(did: str)get_service_descriptors(agent_address: str)query_agents_by_service(service_type_hash: str, min_reputation=0, offset=0, limit=100)supports_service(agent_address: str, service_type_hash: str)compute_service_type_hash(service_type: str)validate_service_descriptors(descriptors: list[dict])build_did(address: str)
Both SDKs enforce full did:ethr:<chainId>:<lowercase-address> format and reject mismatched chain IDs when calling getAgentByDID. Always include the chain ID and lowercase address.
client.events​
The events module provides real-time blockchain event monitoring.
Python SDK uses web3.py filters; polling examples are provided in PY tabs. There are no built-in async watchers yet.
watchTransaction()​
🟡 IntermediateWatches for state changes on a specific transaction.
- TypeScript
- Python
watchTransaction(
txId: string,
callback: (state: State) => void
): () => void
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID to watch |
callback | (state: State) => void | Yes | Callback for state changes |
Returns​
() => void - Cleanup function to stop watching
Example​
const unsubscribe = client.events.watchTransaction(txId, (state) => {
console.log('New state:', State[state]);
if (state === State.DELIVERED) {
console.log('Provider delivered!');
}
});
// Later: stop watching
unsubscribe();
event_filter = client.kernel.events.StateTransitioned.create_filter(
argument_filters={"transactionId": tx_id},
fromBlock="latest",
)
def poll():
for evt in event_filter.get_new_entries():
to_state = evt["args"]["toState"]
print("New state:", to_state)
if to_state == State.DELIVERED:
print("Provider delivered!")
# Call poll() periodically; no built-in watcher helper in Python yet.
waitForState()​
🟡 IntermediateWaits for a transaction to reach a specific state.
- TypeScript
- Python
async waitForState(
txId: string,
targetState: State,
timeoutMs?: number
): Promise<void>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
txId | string | Yes | Transaction ID |
targetState | State | Yes | Target state to wait for |
timeoutMs | number | No | Timeout in milliseconds (default: 60000) |
Throws​
Error- If timeout reached before state change
Example​
import { State } from '@agirails/sdk';
try {
await client.events.waitForState(
txId,
State.DELIVERED,
120000 // 2 minute timeout
);
console.log('Provider delivered!');
} catch (error) {
console.error('Timeout waiting for delivery');
}
import time
from agirails_sdk import State
def wait_for_state(tx_id: str, target: State, timeout_seconds: int = 60):
start = time.time()
while True:
tx = client.get_transaction(tx_id)
if tx.state == target:
return tx
if time.time() - start > timeout_seconds:
raise TimeoutError(f"Timed out waiting for state {target}")
time.sleep(5)
getTransactionHistory()​
Gets all transactions for an address.
- TypeScript
- Python
async getTransactionHistory(
address: string,
role?: 'requester' | 'provider'
): Promise<Transaction[]>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
address | string | Yes | Address to query |
role | 'requester' | 'provider' | No | Filter by role (default: 'requester') |
Returns​
Promise<Transaction[]> - Array of transactions
Example​
// Get all transactions where I'm the requester
const myRequests = await client.events.getTransactionHistory(
await client.getAddress(),
'requester'
);
// Get all transactions where I'm the provider
const myJobs = await client.events.getTransactionHistory(
await client.getAddress(),
'provider'
);
from web3 import Web3
# Build history via event filters on TransactionCreated
created_filter = client.kernel.events.TransactionCreated.create_filter(
argument_filters={"requester": client.address},
fromBlock="earliest",
)
tx_ids = [evt["args"]["transactionId"] for evt in created_filter.get_all_entries()]
txs = [client.get_transaction(tx_id) for tx_id in tx_ids]
onTransactionCreated()​
Subscribes to all new transaction creation events.
- TypeScript
- Python
onTransactionCreated(
callback: (tx: { txId: string; provider: string; requester: string; amount: bigint }) => void
): () => void
Returns​
() => void - Cleanup function to unsubscribe
Example​
const unsubscribe = client.events.onTransactionCreated((tx) => {
console.log('New transaction:', tx.txId);
console.log('Amount:', tx.amount.toString());
});
// Later: stop listening
unsubscribe();
# Python SDK: use event filter and poll
created_filter = client.kernel.events.TransactionCreated.create_filter(
fromBlock="latest",
)
def poll_created():
for evt in created_filter.get_new_entries():
tx_id = evt["args"]["transactionId"]
provider = evt["args"]["provider"]
requester = evt["args"]["requester"]
amount = evt["args"]["amount"]
print("New transaction:", tx_id, provider, requester, amount)
# Call poll_created() periodically.
onStateChanged()​
Subscribes to all state change events.
- TypeScript
- Python
onStateChanged(
callback: (txId: string, from: State, to: State) => void
): () => void
Returns​
() => void - Cleanup function
Example​
const unsubscribe = client.events.onStateChanged((txId, from, to) => {
console.log(`${txId}: ${State[from]} -> ${State[to]}`);
});
state_filter = client.kernel.events.StateTransitioned.create_filter(
fromBlock="latest",
)
def poll_state_changes():
for evt in state_filter.get_new_entries():
tx_id = evt["args"]["transactionId"]
from_state = evt["args"]["fromState"]
to_state = evt["args"]["toState"]
print(f"{tx_id}: {from_state} -> {to_state}")
onEscrowReleased()​
Subscribes to escrow release events.
- TypeScript
- Python
onEscrowReleased(
callback: (txId: string, amount: bigint) => void
): () => void
Returns​
() => void - Cleanup function
Example​
const unsubscribe = client.events.onEscrowReleased((txId, amount) => {
console.log(`Escrow released for ${txId}: ${amount.toString()}`);
});
event_filter = client.kernel.events.EscrowReleased.create_filter(fromBlock="latest")
def poll_escrow_released():
for evt in event_filter.get_new_entries():
tx_id = evt["args"]["transactionId"]
amount = evt["args"]["amount"]
print(f"Escrow released for {tx_id}: {amount}")
Returns​
Manual polling; implement your own loop/scheduler.
client.quote​
The quote builder module handles AIP-2 price quote construction.
Python SDK exposes quote helpers via MessageSigner + QuoteBuilder. See Python tabs below.
build()​
🟡 IntermediateBuilds and signs a price quote message.
async build(params: QuoteParams): Promise<QuoteMessage>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
params.txId | string | Yes | Transaction ID (bytes32) |
params.provider | string | Yes | Provider DID (did:ethr:chainId:address) |
params.consumer | string | Yes | Consumer DID |
params.quotedAmount | string | Yes | Quoted price in base units (string) |
params.originalAmount | string | Yes | Consumer's original offer |
params.maxPrice | string | Yes | Consumer's maximum price |
params.currency | string | No | Currency (default: 'USDC') |
params.decimals | number | No | Token decimals (default: 6) |
params.expiresAt | number | No | Expiration timestamp (default: +1 hour) |
params.justification | object | No | Optional quote justification |
params.chainId | number | Yes | Chain ID (84532 or 8453) |
params.kernelAddress | string | Yes | ACTPKernel contract address |
Returns​
interface QuoteMessage {
type: 'agirails.quote.v1';
version: '1.0.0';
txId: string;
provider: string;
consumer: string;
quotedAmount: string;
originalAmount: string;
maxPrice: string;
currency: string;
decimals: number;
quotedAt: number;
expiresAt: number;
justification?: object;
chainId: number;
nonce: number;
signature: string;
}
Example​
- TypeScript
- Python
const quote = await client.quote.build({
txId,
provider: 'did:ethr:84532:0xProvider...',
consumer: 'did:ethr:84532:0xConsumer...',
quotedAmount: '7500000', // $7.50 USDC
originalAmount: '5000000', // $5.00 original offer
maxPrice: '10000000', // $10.00 maximum
chainId: 84532,
kernelAddress: config.contracts.actpKernel,
justification: {
reason: 'Additional compute resources required',
estimatedTime: 300
}
});
console.log('Quote signature:', quote.signature);
import os, time
from agirails_sdk.message_signer import MessageSigner
from agirails_sdk.quote_builder import QuoteBuilder
signer = MessageSigner(private_key=os.getenv("PRIVATE_KEY"))
signer.init_domain(kernel_address=config.contracts.actpKernel, chain_id=84532)
builder = QuoteBuilder(signer)
quote = builder.build(
tx_id=tx_id,
provider="did:ethr:84532:0xProvider...",
consumer="did:ethr:84532:0xConsumer...",
quoted_amount="7500000", # $7.50 USDC
original_amount="5000000", # $5.00 original offer
max_price="10000000", # $10.00 maximum
chain_id=84532,
kernel_address=config.contracts.actpKernel,
justification={
"reason": "Additional compute resources required",
"estimatedTime": 300,
},
)
print("Quote signature:", quote["signature"])
verify()​
Verifies a quote's signature and business rules.
- TypeScript
- Python
async verify(
quote: QuoteMessage,
kernelAddress: string
): Promise<boolean>
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
quote | QuoteMessage | Yes | Quote message to verify |
kernelAddress | string | Yes | ACTPKernel contract address |
Returns​
Promise<boolean> - true if valid
Throws​
Error- If signature invalid, amounts invalid, or quote expired
Example​
try {
await client.quote.verify(quote, config.contracts.actpKernel);
console.log('Quote is valid!');
} catch (error) {
console.error('Invalid quote:', error.message);
}
from agirails_sdk.quote_builder import QuoteBuilder
from agirails_sdk.message_signer import MessageSigner
signer = MessageSigner(private_key=os.getenv("PRIVATE_KEY"))
signer.init_domain(kernel_address=config.contracts.actpKernel, chain_id=84532)
builder = QuoteBuilder(signer)
try:
builder.verify(quote, config.contracts.actpKernel)
print("Quote is valid!")
except Exception as error:
print("Invalid quote:", error)
computeHash()​
Computes the keccak256 hash of a quote for on-chain storage.
- TypeScript
- Python
computeHash(quote: QuoteMessage): string
Returns​
string - Keccak256 hash (0x-prefixed)
Example​
const quoteHash = client.quote.computeHash(quote);
await client.kernel.submitQuote(txId, quoteHash);
from agirails_sdk.quote_builder import canonical_json_stringify
from web3 import Web3
canonical = canonical_json_stringify(quote)
quote_hash = Web3.keccak(text=canonical).hex()
print("Quote hash:", quote_hash)
uploadToIPFS()​
Uploads a quote to IPFS (requires IPFS client configuration).
async uploadToIPFS(quote: QuoteMessage): Promise<string>
Returns​
Promise<string> - IPFS CID
Throws​
Error- If IPFS client not configured
client.proofGenerator​
The proof generator module creates content hashes and delivery proofs.
Python SDK exposes proof helpers via ProofGenerator (hash, generate, encode/decode, verify, hash_from_url).
hashContent()​
🟢 BasicHashes content using Keccak256.
- TypeScript
- Python
hashContent(content: string | Buffer): string
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Buffer | Yes | Content to hash |
Returns​
string - Keccak256 hash (0x-prefixed)
Example​
const hash = client.proofGenerator.hashContent('Hello, World!');
console.log('Content hash:', hash);
from agirails_sdk.proof_generator import ProofGenerator
hash_hex = ProofGenerator.hash_content("Hello, World!")
print("Content hash:", hash_hex)
generateDeliveryProof()​
🟡 IntermediateGenerates a complete delivery proof object.
- TypeScript
- Python
generateDeliveryProof(params: {
txId: string;
deliverable: string | Buffer;
deliveryUrl?: string;
metadata?: Record<string, any>;
}): DeliveryProof
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
params.txId | string | Yes | Transaction ID |
params.deliverable | string | Buffer | Yes | Deliverable content |
params.deliveryUrl | string | No | IPFS/Arweave URL |
params.metadata | object | No | Additional metadata |
Returns​
interface DeliveryProof {
type: 'delivery.proof';
txId: string;
contentHash: string;
timestamp: number;
deliveryUrl?: string;
metadata: {
size: number;
mimeType: string;
[key: string]: any;
};
}
Example​
const proof = client.proofGenerator.generateDeliveryProof({
txId,
deliverable: JSON.stringify({ result: 'Analysis complete', data: [...] }),
deliveryUrl: 'ipfs://QmXxx...',
metadata: { mimeType: 'application/json' }
});
console.log('Content hash:', proof.contentHash);
console.log('Size:', proof.metadata.size, 'bytes');
from agirails_sdk.proof_generator import ProofGenerator
proof = ProofGenerator.generate_delivery_proof(
tx_id="0x123...",
deliverable="Completed analysis report...",
delivery_url="ipfs://Qm...",
metadata={"mimeType": "application/json"},
)
print("Content hash:", proof["contentHash"])
print("Size:", proof["metadata"]["size"])
encodeProof()​
Encodes a proof for on-chain submission.
- TypeScript
- Python
encodeProof(proof: DeliveryProof): BytesLike
Returns​
BytesLike - ABI-encoded proof data
const encoded = client.proofGenerator.encodeProof(proof);
from agirails_sdk.proof_generator import ProofGenerator
encoded = ProofGenerator.encode_proof(proof)
decodeProof()​
Decodes proof data from on-chain.
- TypeScript
- Python
decodeProof(proofData: BytesLike): {
txId: string;
contentHash: string;
timestamp: number;
}
from agirails_sdk.proof_generator import ProofGenerator
decoded = ProofGenerator.decode_proof(encoded_bytes)
print(decoded["txId"], decoded["contentHash"], decoded["timestamp"])
verifyDeliverable()​
Verifies deliverable content matches expected hash.
- TypeScript
- Python
verifyDeliverable(
deliverable: string | Buffer,
expectedHash: string
): boolean
const isValid = client.proofGenerator.verifyDeliverable(
deliveredContent,
proof.contentHash
);
if (!isValid) {
throw new Error('Content does not match proof!');
}
from agirails_sdk.proof_generator import ProofGenerator
is_valid = ProofGenerator.verify_deliverable(delivered_content, proof["contentHash"])
if not is_valid:
raise ValueError("Content does not match proof!")
hashFromUrl()​
Fetches content from URL and computes hash.
- TypeScript
- Python
async hashFromUrl(url: string): Promise<string>
# requires aiohttp
import asyncio
from agirails_sdk.proof_generator import ProofGenerator
async def run():
h = await ProofGenerator.hash_from_url("https://example.com/file.txt")
print(h)
asyncio.run(run())
toDeliveryProofTypedData()​
Converts a generated DeliveryProof to EIP-712 typed data format for signing.
toDeliveryProofTypedData(proof: DeliveryProof): DeliveryProofData
Python SDK does not expose this helper; build typed data manually if needed when using MessageSigner.sign_delivery_proof.
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
proof | DeliveryProof | Yes | Delivery proof object from generateDeliveryProof() |
Returns​
interface DeliveryProofData {
txId: string;
contentHash: string;
timestamp: number;
deliveryUrl: string;
size: number;
mimeType: string;
}
Example​
const proof = client.proofGenerator.generateDeliveryProof({
txId,
deliverable: 'Result data...',
deliveryUrl: 'ipfs://Qm...',
metadata: { mimeType: 'text/plain' }
});
// Convert to EIP-712 typed data for signing
const typedData = client.proofGenerator.toDeliveryProofTypedData(proof);
console.log('Typed data:', typedData);
client.messageSigner​
The message signer module handles EIP-712 message signing for ACTP.
Python SDK exposes MessageSigner helpers (init_domain, sign_quote, sign_delivery_proof, verify).
initDomain()​
Initializes the EIP-712 domain (called automatically by ACTPClient.create()).
async initDomain(kernelAddress: string, chainId?: number): Promise<void>
Python usage (MessageSigner):
from agirails_sdk.message_signer import MessageSigner
signer = MessageSigner(private_key=os.getenv("PRIVATE_KEY"), chain_id=84532)
signer.init_domain(kernel_address="0xKernelAddress", name="ACTP", version="1.0")
# Sign quote (AIP-2)
signature = signer.sign_quote(message_dict)
# Sign delivery proof (AIP-4)
signature_dp = signer.sign_delivery_proof(proof_dict)
signMessage()​
Signs a generic ACTP message.
async signMessage(message: ACTPMessage): Promise<string>
Returns​
Promise<string> - EIP-712 signature
signQuoteRequest()​
Signs a typed quote request.
async signQuoteRequest(data: QuoteRequestData): Promise<string>
signQuoteResponse()​
Signs a typed quote response.
async signQuoteResponse(data: QuoteResponseData): Promise<string>
signDeliveryProof()​
Signs a typed delivery proof.
async signDeliveryProof(data: DeliveryProofData): Promise<string>
signGeneratedDeliveryProof()​
Signs a delivery proof from ProofGenerator.
- TypeScript
- Python
async signGeneratedDeliveryProof(proof: DeliveryProof): Promise<string>
Example​
const proof = client.proofGenerator.generateDeliveryProof({...});
const signature = await client.messageSigner.signGeneratedDeliveryProof(proof);
from agirails_sdk.message_signer import MessageSigner
signer = MessageSigner(private_key=os.getenv("PRIVATE_KEY"))
signer.init_domain(kernel_address="0xKernelAddress", chain_id=84532)
# Sign quote or delivery proof typed data
quote_signature = signer.sign_quote(quote_message_dict)
delivery_signature = signer.sign_delivery_proof(delivery_proof_dict)
verifySignature()​
Verifies message signature.
- TypeScript
- Python
async verifySignature(
message: ACTPMessage,
signature: string
): Promise<boolean>
Returns​
Promise<boolean> - true if signature is valid
const isValid = await client.messageSigner.verifySignature(message, signature);
from agirails_sdk.message_signer import MessageSigner
is_valid = MessageSigner.verify_signature(
typed_data, # EIP-712 typed data dict
signature,
expected_signer,
)
print("Signature valid:", is_valid)
verifySignatureOrThrow()​
🔴 AdvancedVerifies signature and throws if invalid.
async verifySignatureOrThrow(
message: ACTPMessage,
signature: string
): Promise<void>
Throws​
SignatureVerificationError- If signature verification failsError- If nonce replay detected (when tracker configured)
addressToDID()​
Converts Ethereum address to DID format.
addressToDID(address: string): string
Example​
const did = client.messageSigner.addressToDID('0x742d35...');
// Returns: "did:ethr:0x742d35..."
Types & Interfaces​
State​
Enum representing transaction states.
enum State {
INITIATED = 0, // Transaction created, awaiting escrow
QUOTED = 1, // Provider submitted quote (optional)
COMMITTED = 2, // Escrow linked, work can begin
IN_PROGRESS = 3, // Provider working (required)
DELIVERED = 4, // Provider delivered result
SETTLED = 5, // Funds released (terminal)
DISPUTED = 6, // Under dispute
CANCELLED = 7 // Cancelled (terminal)
}
Transaction​
Interface for transaction data.
interface Transaction {
txId: string; // Transaction ID (bytes32)
requester: string; // Requester address
provider: string; // Provider address
amount: bigint; // Amount in USDC (6 decimals)
state: State; // Current state
createdAt: number; // Creation timestamp
deadline: number; // Deadline timestamp
disputeWindow: number; // Dispute window (seconds)
escrowContract: string; // Escrow contract address
escrowId: string; // Escrow ID
metadata: string; // Service hash/quote hash
}
CreateTransactionParams​
Parameters for creating a transaction.
interface CreateTransactionParams {
provider: string; // Provider address
requester: string; // Requester address
amount: bigint; // Amount in USDC
deadline: number; // Unix timestamp
disputeWindow: number; // Seconds
metadata?: string; // Optional bytes32
}
DisputeResolution​
Parameters for dispute resolution.
interface DisputeResolution {
requesterAmount: bigint; // Refund to requester
providerAmount: bigint; // Payment to provider
mediatorAmount: bigint; // Mediator fee
mediator?: string; // Mediator address
}
EconomicParams​
Platform economic parameters.
interface EconomicParams {
baseFeeNumerator: number; // Fee numerator (100 = 1%)
baseFeeDenominator: number; // Always 10000
feeRecipient: string; // Fee recipient address
requesterPenaltyBps: number; // Penalty for false disputes
providerPenaltyBps: number; // Reserved
}
NetworkConfig​
Network configuration.
interface NetworkConfig {
name: string;
chainId: number;
rpcUrl: string;
blockExplorer: string;
contracts: {
actpKernel: string;
escrowVault: string;
usdc: string;
eas: string;
easSchemaRegistry: string;
};
eas: {
deliverySchemaUID: string;
};
gasSettings: {
maxFeePerGas: bigint;
maxPriorityFeePerGas: bigint;
};
}
Escrow​
Escrow state.
interface Escrow {
escrowId: string;
kernel: string;
txId: string;
token: string;
amount: bigint;
beneficiary: string;
createdAt: number;
released: boolean;
}
DeliveryProof​
Delivery proof object.
interface DeliveryProof {
type: 'delivery.proof';
txId: string;
contentHash: string;
timestamp: number;
deliveryUrl?: string;
metadata: {
size: number;
mimeType: string;
[key: string]: any;
};
}
QuoteMessage​
AIP-2 price quote message.
interface QuoteMessage {
type: 'agirails.quote.v1';
version: '1.0.0';
txId: string;
provider: string;
consumer: string;
quotedAmount: string;
originalAmount: string;
maxPrice: string;
currency: string;
decimals: number;
quotedAt: number;
expiresAt: number;
justification?: {
reason?: string;
estimatedTime?: number;
computeCost?: number;
breakdown?: Record<string, any>;
};
chainId: number;
nonce: number;
signature: string;
}
ACTPClientConfig​
Client configuration.
interface ACTPClientConfig {
network: 'base-sepolia' | 'base-mainnet';
privateKey?: string;
signer?: Signer;
provider?: JsonRpcProvider;
rpcUrl?: string;
contracts?: {
actpKernel?: string;
escrowVault?: string;
usdc?: string;
};
gasSettings?: {
maxFeePerGas?: bigint;
maxPriorityFeePerGas?: bigint;
};
eas?: EASConfig;
}
EASConfig​
EAS configuration.
interface EASConfig {
contractAddress: string;
deliveryProofSchemaId: string;
}
DeliveryProofMessage​
AIP-4 v1.1 delivery proof message format with EAS attestation support.
interface DeliveryProofMessage {
type: 'agirails.delivery.v1';
version: string; // Semantic version (e.g., "1.0.0")
txId: string; // bytes32 (0x-prefixed)
provider: string; // DID (e.g., "did:ethr:84532:0x...")
consumer: string; // DID
resultCID: string; // IPFS CID (CIDv1, base32)
resultHash: string; // Keccak256 hash of canonical result JSON
metadata?: {
executionTime?: number; // Seconds
outputFormat?: string; // MIME type
outputSize?: number; // Bytes
notes?: string; // Max 500 chars
};
easAttestationUID: string; // bytes32 (0x-prefixed)
deliveredAt: number; // Unix timestamp (seconds)
chainId: number; // 84532 or 8453
nonce: number; // Monotonically increasing
signature: string; // EIP-712 signature
}
EASAttestationData​
EAS attestation data structure for delivery proofs.
interface EASAttestationData {
schema: string; // EAS schema UID
recipient: string; // Provider address
expirationTime: number; // Unix timestamp (0 for no expiration)
revocable: boolean; // Whether attestation can be revoked
refUID: string; // Reference to transaction
data: string; // ABI-encoded attestation data
}
QuoteMessageV2​
AIP-2 v2 quote message format for price negotiations.
interface QuoteMessageV2 {
type: 'agirails.quote.v1';
version: '1.0.0';
txId: string; // bytes32 (0x-prefixed)
provider: string; // DID
consumer: string; // DID
quotedAmount: string; // Provider's quoted price (base units)
originalAmount: string; // Consumer's original offer
maxPrice: string; // Consumer's maximum acceptable price
currency: string; // Currently "USDC" only
decimals: number; // Token decimals (6 for USDC)
quotedAt: number; // Unix timestamp (seconds)
expiresAt: number; // Unix timestamp (seconds)
justification?: {
reason?: string;
estimatedTime?: number;
computeCost?: number;
breakdown?: Record<string, any>;
};
chainId: number; // 84532 or 8453
nonce: number; // Monotonically increasing
signature: string; // EIP-712 signature
}
Utility Functions​
The SDK exports several utility functions for common operations.
canonicalJsonStringify()​
Deterministic JSON serialization with sorted keys and no whitespace. Uses fast-json-stable-stringify for cross-language compatibility.
- TypeScript
- Python
import { canonicalJsonStringify } from '@agirails/sdk';
canonicalJsonStringify(obj: any): string
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
obj | any | Yes | Any JSON-serializable object |
Returns​
string - Canonical JSON string with sorted keys
Example​
import { canonicalJsonStringify } from '@agirails/sdk';
const obj = { z: 1, a: 2, m: { b: 3, a: 4 } };
const canonical = canonicalJsonStringify(obj);
console.log(canonical);
// Output: {"a":2,"m":{"a":4,"b":3},"z":1}
import json
def canonical_json_stringify(obj) -> str:
return json.dumps(obj, sort_keys=True, separators=(",", ":"))
obj = {"z": 1, "a": 2, "m": {"b": 3, "a": 4}}
canonical = canonical_json_stringify(obj)
print(canonical)
# Output: {"a":2,"m":{"a":4,"b":3},"z":1}
Python SDK does not expose a helper; use sorted json.dumps as above.
computeCanonicalHash()​
Computes Keccak256 hash of canonical JSON representation.
- TypeScript
- Python
import { computeCanonicalHash } from '@agirails/sdk';
computeCanonicalHash(obj: any): string
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
obj | any | Yes | Any JSON-serializable object |
Returns​
string - Keccak256 hash (0x-prefixed hex string)
Example​
import { computeCanonicalHash } from '@agirails/sdk';
const data = { result: 'success', value: 42 };
const hash = computeCanonicalHash(data);
console.log(hash);
// Output: 0x1234...abcd (64 hex chars)
import json
from web3 import Web3
def compute_canonical_hash(obj) -> str:
canonical = json.dumps(obj, sort_keys=True, separators=(",", ":"))
return Web3.keccak(text=canonical).hex()
data = {"result": "success", "value": 42}
hash_hex = compute_canonical_hash(data)
print(hash_hex)
computeResultHash()​
Computes hash for delivery proof result data. Alias for computeCanonicalHash() with semantic naming for AIP-4.
- TypeScript
- Python
import { computeResultHash } from '@agirails/sdk';
computeResultHash(resultData: any): string
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
resultData | any | Yes | Service result data |
Returns​
string - Keccak256 hash of canonical result JSON
Example​
import { computeResultHash } from '@agirails/sdk';
const result = { analysis: 'Complete', score: 95 };
const hash = computeResultHash(result);
// Use in delivery proof
# Alias of compute_canonical_hash in Python example above
result = {"analysis": "Complete", "score": 95}
hash_hex = compute_canonical_hash(result)
print("Result hash:", hash_hex)
validateAddress()​
Validates an Ethereum address format and checks for zero address.
import { validateAddress } from '@agirails/sdk';
validateAddress(address: string, fieldName?: string): void
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
address | string | Yes | Ethereum address to validate |
fieldName | string | No | Field name for error messages (default: "address") |
Throws​
InvalidAddressError- If address format is invalidValidationError- If address is zero address
Example​
import { validateAddress } from '@agirails/sdk';
try {
validateAddress('0x742d35Cc6634C0532925a3b844Bc454e4438f44e');
console.log('Valid address');
} catch (error) {
console.error('Invalid:', error.message);
}
// With custom field name
validateAddress(providerAddress, 'provider');
validateAmount()​
Validates that an amount is greater than zero.
import { validateAmount } from '@agirails/sdk';
validateAmount(amount: bigint, fieldName?: string): void
Parameters​
| Name | Type | Required | Description |
|---|---|---|---|
amount | bigint | Yes | Amount to validate |
fieldName | string | No | Field name for error messages (default: "amount") |
Throws​
InvalidAmountError- If amount is null, undefined, or ≤ 0
Example​
import { validateAmount } from '@agirails/sdk';
import { parseUnits } from 'ethers';
const amount = parseUnits('100', 6);
validateAmount(amount); // Passes
validateAmount(0n); // Throws InvalidAmountError
validateAmount(-1n); // Throws InvalidAmountError
EIP-712 Types​
The SDK exports EIP-712 type definitions for advanced use cases such as custom signing or verification.
EIP712Domain​
Standard EIP-712 domain separator for ACTP messages.
interface EIP712Domain {
name: string; // "AGIRAILS"
version: string; // "1"
chainId: number; // 84532 or 8453
verifyingContract: string; // ACTPKernel address
}
Python SDK does not expose EIP-712 domain helpers; construct manually when signing with eth_account/web3.py if needed.
AIP4DeliveryProofTypes​
EIP-712 type definition for AIP-4 v1.1 delivery proofs.
const AIP4DeliveryProofTypes = {
DeliveryProof: [
{ name: 'txId', type: 'bytes32' },
{ name: 'provider', type: 'string' },
{ name: 'consumer', type: 'string' },
{ name: 'resultCID', type: 'string' },
{ name: 'resultHash', type: 'bytes32' },
{ name: 'easAttestationUID', type: 'bytes32' },
{ name: 'deliveredAt', type: 'uint256' },
{ name: 'chainId', type: 'uint256' },
{ name: 'nonce', type: 'uint256' }
]
};
DeliveryProofTypes​
EIP-712 type definition for legacy delivery proofs (deprecated, use AIP4DeliveryProofTypes).
- TypeScript
- Python
const DeliveryProofTypes = {
DeliveryProof: [
{ name: 'txId', type: 'bytes32' },
{ name: 'contentHash', type: 'bytes32' },
{ name: 'timestamp', type: 'uint256' },
{ name: 'deliveryUrl', type: 'string' },
{ name: 'size', type: 'uint256' },
{ name: 'mimeType', type: 'string' }
]
};
Python SDK does not expose EIP-712 type constants. For Python signing/verification, use MessageSigner.sign_delivery_proof and ProofGenerator.encode_proof; construct typed data manually if you need custom EIP-712 verification with eth_account.
Usage Example​
import {
EIP712Domain,
AIP4DeliveryProofTypes,
AIP4DeliveryProofData
} from '@agirails/sdk';
import { verifyTypedData } from 'ethers';
// Verify a delivery proof signature
const domain: EIP712Domain = {
name: 'AGIRAILS',
version: '1',
chainId: 84532,
verifyingContract: kernelAddress
};
const message: AIP4DeliveryProofData = {
txId: proof.txId,
provider: proof.provider,
consumer: proof.consumer,
resultCID: proof.resultCID,
resultHash: proof.resultHash,
easAttestationUID: proof.easAttestationUID,
deliveredAt: proof.deliveredAt,
chainId: proof.chainId,
nonce: proof.nonce
};
const recoveredAddress = verifyTypedData(
domain,
AIP4DeliveryProofTypes,
message,
proof.signature
);
Errors​
Python SDK raises ValidationError, TransactionError, RpcError, InvalidStateTransitionError, DeadlineError, and generic Exception equivalents. Map TS errors roughly as:
TransactionRevertedError→TransactionErrorNetworkError→RpcErrorInsufficientFundsError→TransactionError(with revert reason)
ACTPError​
Base error class for all SDK errors.
class ACTPError extends Error {
code: string; // Error code
txHash?: string; // Transaction hash (if applicable)
details?: any; // Additional details
}
ValidationError​
Thrown for invalid input parameters.
class ValidationError extends ACTPError {
constructor(field: string, message: string)
}
Example:
try {
await client.kernel.createTransaction({
amount: -100n, // Invalid!
// ...
});
} catch (error) {
if (error instanceof ValidationError) {
console.error('Field:', error.details.field); // "amount"
}
}
TransactionNotFoundError​
Thrown when a transaction ID does not exist.
- TypeScript
- Python
class TransactionNotFoundError extends ACTPError {
constructor(txId: string)
}
Example:
try {
await client.kernel.getTransaction('0xinvalid...');
} catch (error) {
if (error instanceof TransactionNotFoundError) {
console.error('Transaction not found:', error.details.txId);
}
}
from agirails_sdk.errors import ACTPClientError
try:
client.get_transaction("0xinvalid...")
except ACTPClientError as error:
# Python SDK does not expose a specific TransactionNotFoundError;
# it raises ACTPClientError from RPC; inspect message/code if needed.
print("Transaction not found:", error)
TransactionRevertedError​
Thrown when an on-chain transaction reverts.
- TypeScript
- Python
class TransactionRevertedError extends ACTPError {
constructor(txHash: string, reason?: string)
}
Example:
try {
await client.kernel.releaseEscrow(txId);
} catch (error) {
if (error instanceof TransactionRevertedError) {
console.error('Reverted:', error.details.reason);
console.error('Tx hash:', error.txHash);
}
}
from agirails_sdk.errors import TransactionError
try:
client.release_escrow(tx_id)
except TransactionError as error:
# Inspect error message / RPC revert reason
print("Reverted:", error)
InvalidStateTransitionError​
Thrown for invalid state machine transitions.
- TypeScript
- Python
class InvalidStateTransitionError extends ACTPError {
constructor(from: State, to: State, validTransitions: string[])
}
Example:
try {
// Can't go from INITIATED directly to DELIVERED
await client.kernel.transitionState(txId, State.DELIVERED);
} catch (error) {
if (error instanceof InvalidStateTransitionError) {
console.error('From:', error.details.from);
console.error('Valid transitions:', error.details.validTransitions);
}
}
from agirails_sdk.errors import InvalidStateTransitionError
from agirails_sdk import State
try:
client.transition_state(tx_id, State.DELIVERED)
except InvalidStateTransitionError as error:
print("Invalid transition:", error)
NetworkError​
Thrown for network connectivity issues.
class NetworkError extends ACTPError {
constructor(network: string, message: string)
}
SignatureVerificationError​
Thrown when signature verification fails.
- TypeScript
- Python
class SignatureVerificationError extends ACTPError {
constructor(expectedSigner: string, recoveredSigner: string)
}
from agirails_sdk.errors import ValidationError
# Python SDK surfaces signature validation issues as ValidationError
# or TransactionError depending on context.
InsufficientFundsError​
Thrown when account has insufficient balance.
- TypeScript
- Python
class InsufficientFundsError extends ACTPError {
constructor(required: bigint, available: bigint)
}
from agirails_sdk.errors import TransactionError
try:
client.fund_transaction("0x123...")
except TransactionError as error:
# Inspect revert reason for insufficient funds / allowance
print("Insufficient funds or allowance:", error)
DeadlineExpiredError​
Thrown when transaction deadline has passed.
- TypeScript
- Python
class DeadlineExpiredError extends ACTPError {
constructor(txId: string, deadline: number)
}
from agirails_sdk.errors import DeadlineError
try:
client.transition_state(tx_id, State.IN_PROGRESS)
except DeadlineError as error:
print("Deadline expired:", error)
Error Recovery​
Best practices for handling errors in production.
Retry with Exponential Backoff​
- TypeScript
- Python
import { ACTPClient, TransactionRevertedError, NetworkError } from '@agirails/sdk';
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
// Exponential backoff: 1s, 2s, 4s
const delay = baseDelay * Math.pow(2, attempt);
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('Max retries exceeded');
}
// Usage
const txId = await withRetry(() =>
client.kernel.createTransaction({...})
);
import time
from agirails_sdk.errors import RpcError, TransactionError
def with_retry(fn, max_retries: int = 3, base_delay: float = 1.0):
for attempt in range(max_retries):
try:
return fn()
except (RpcError, TransactionError) as error:
if attempt == max_retries - 1:
raise
delay = base_delay * (2 ** attempt) # 1s, 2s, 4s
print(f"Attempt {attempt + 1} failed, retrying in {delay}s...")
time.sleep(delay)
# Usage
tx_id = with_retry(lambda: client.create_transaction(...))
Handle Specific Errors​
- TypeScript
- Python
import {
ValidationError,
TransactionRevertedError,
NetworkError,
InsufficientFundsError,
DeadlineExpiredError
} from '@agirails/sdk';
async function safeCreateTransaction(params: CreateTransactionParams) {
try {
return await client.kernel.createTransaction(params);
} catch (error) {
if (error instanceof ValidationError) {
// Bad input - fix parameters
console.error('Invalid parameter:', error.details.field);
throw error; // Don't retry validation errors
}
if (error instanceof InsufficientFundsError) {
// Need more USDC
console.error('Need', error.details.required.toString(), 'but have', error.details.available.toString());
throw error;
}
if (error instanceof NetworkError) {
// Network issue - safe to retry
console.error('Network error:', error.message);
return withRetry(() => client.kernel.createTransaction(params));
}
if (error instanceof TransactionRevertedError) {
// Check revert reason
if (error.details.reason?.includes('nonce')) {
// Nonce issue - wait and retry
await new Promise(r => setTimeout(r, 2000));
return client.kernel.createTransaction(params);
}
throw error;
}
throw error;
}
}
from agirails_sdk.errors import (
ValidationError,
TransactionError,
RpcError,
DeadlineError,
)
def safe_create_transaction(params: dict):
try:
return client.create_transaction(**params)
except ValidationError as error:
print("Invalid parameter:", error)
raise
except RpcError as error:
print("Network/RPC error:", error)
return with_retry(lambda: client.create_transaction(**params))
except TransactionError as error:
# Inspect error message; if nonce-related, wait and retry
msg = str(error).lower()
if "nonce" in msg:
time.sleep(2)
return client.create_transaction(**params)
raise
Graceful Degradation​
- TypeScript
- Python
async function getTransactionSafe(txId: string): Promise<Transaction | null> {
try {
return await client.kernel.getTransaction(txId);
} catch (error) {
if (error instanceof TransactionNotFoundError) {
return null; // Expected case
}
if (error instanceof NetworkError) {
console.warn('Network issue, using cached data');
return getCachedTransaction(txId);
}
throw error;
}
}
from agirails_sdk.errors import ACTPClientError
def get_transaction_safe(tx_id: str):
try:
return client.get_transaction(tx_id)
except ACTPClientError as error:
if "not found" in str(error).lower():
return None
print("Network issue or other error:", error)
# fallback: return cached transaction if you maintain one
return None
Constants​
Contract Addresses​
Base Sepolia (Testnet)​
- TypeScript
- Python
import { BASE_SEPOLIA } from '@agirails/sdk';
BASE_SEPOLIA.contracts.actpKernel // "0x6aDB650e185b0ee77981AC5279271f0Fa6CFe7ba"
BASE_SEPOLIA.contracts.escrowVault // "0x921edE340770db5DB6059B5B866be987d1b7311F"
BASE_SEPOLIA.contracts.usdc // "0x444b4e1A65949AB2ac75979D5d0166Eb7A248Ccb" (MockUSDC)
BASE_SEPOLIA.contracts.eas // "0x4200000000000000000000000000000000000021"
BASE_SEPOLIA.chainId // 84532
from agirails_sdk import Network
Network.BASE_SEPOLIA.value # "base-sepolia"
Network.BASE_SEPOLIA.contracts.actp_kernel # "0x6aDB650e185b0ee77981AC5279271f0Fa6CFe7ba"
Network.BASE_SEPOLIA.contracts.escrow_vault # "0x921edE340770db5DB6059B5B866be987d1b7311F"
Network.BASE_SEPOLIA.contracts.usdc # "0x444b4e1A65949AB2ac75979D5d0166Eb7A248Ccb"
Network.BASE_SEPOLIA.contracts.eas # "0x4200000000000000000000000000000000000021"
Network.BASE_SEPOLIA.chain_id # 84532
Base Mainnet​
- TypeScript
- Python
import { BASE_MAINNET } from '@agirails/sdk';
BASE_MAINNET.contracts.usdc // "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" (Official USDC)
BASE_MAINNET.chainId // 8453
from agirails_sdk import Network
Network.BASE_MAINNET.contracts.usdc # "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
Network.BASE_MAINNET.chain_id # 8453
ACTPKernel and EscrowVault are not yet deployed to Base Mainnet. The addresses are currently zero addresses.
State Values​
- TypeScript
- Python
import { State } from '@agirails/sdk';
State.INITIATED // 0
State.QUOTED // 1
State.COMMITTED // 2
State.IN_PROGRESS // 3
State.DELIVERED // 4
State.SETTLED // 5
State.DISPUTED // 6
State.CANCELLED // 7
from agirails_sdk import State
State.INITIATED # 0
State.QUOTED # 1
State.COMMITTED # 2
State.IN_PROGRESS # 3
State.DELIVERED # 4
State.SETTLED # 5
State.DISPUTED # 6
State.CANCELLED # 7
Network Functions​
- TypeScript
- Python
import { getNetwork, isValidNetwork, NETWORKS } from '@agirails/sdk';
// Get network config
const config = getNetwork('base-sepolia');
// Check if network is valid
isValidNetwork('base-sepolia'); // true
isValidNetwork('ethereum'); // false
// All supported networks
Object.keys(NETWORKS); // ['base-sepolia', 'base-mainnet']
from agirails_sdk import Network
# Validate / list networks (Python uses enum)
Network.BASE_SEPOLIA.name # "BASE_SEPOLIA"
Network.BASE_MAINNET.name # "BASE_MAINNET"
[n.value for n in Network] # ['base-sepolia', 'base-mainnet']
# Access config
net = Network.BASE_SEPOLIA
net.chain_id # 84532
net.contracts.usdc # Mock USDC on testnet
Common Patterns​
Transaction Flow Diagram​
Pattern 1: Requester Happy Path​
Complete flow for a requester creating and settling a payment:
- TypeScript
- Python
import { ACTPClient, State } from '@agirails/sdk';
import { parseUnits } from 'ethers';
async function requesterHappyPath() {
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!,
eas: {
contractAddress: '0x4200000000000000000000000000000000000021',
deliveryProofSchemaId: '0x1b0ebdf0bd20c28ec9d5362571ce8715a55f46e81c3de2f9b0d8e1b95fb5ffce'
}
});
// 1. Create transaction
const txId = await client.kernel.createTransaction({
requester: await client.getAddress(),
provider: '0xProviderAddress...',
amount: parseUnits('10', 6), // 10 USDC
deadline: Math.floor(Date.now() / 1000) + 86400, // 24h
disputeWindow: 7200 // 2h
});
console.log('1. Created:', txId);
// 2. Fund (approve + escrow)
const escrowId = await client.fundTransaction(txId);
console.log('2. Funded:', escrowId);
// 3. Wait for provider to deliver
console.log('3. Waiting for delivery...');
await client.events.waitForState(txId, State.DELIVERED, 3600000); // 1h timeout
// 4. Get attestation and release
const tx = await client.kernel.getTransaction(txId);
await client.releaseEscrowWithVerification(txId, tx.attestationUID);
console.log('4. Released! Payment complete.');
}
from agirails_sdk import ACTPClient, Network, State
def requester_happy_path():
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
)
# 1. Create transaction
tx_id = client.create_transaction(
requester=client.address,
provider="0xProviderAddress...",
amount=10_000_000, # 10 USDC
deadline=client.now() + 86400,
dispute_window=7200,
service_hash="0x" + "00" * 32,
)
print("1. Created:", tx_id)
# 2. Fund (approve + escrow)
escrow_id = client.fund_transaction(tx_id)
print("2. Funded:", escrow_id)
# 3. Poll for delivery (pseudo)
print("3. Waiting for delivery...")
# Implement event polling or polling get_transaction(tx_id).state until DELIVERED
# 4. Release with verification (requires attestation UID)
tx = client.get_transaction(tx_id)
client.release_escrow_with_verification(tx_id, tx.attestation_uid)
print("4. Released! Payment complete.")
Notes​
- Python SDK does not have wait_for_state; poll events or transaction state.
- Pass the attestation UID when calling
release_escrow_with_verification.
Pattern 2: Provider Happy Path​
Complete flow for a provider accepting and completing work:
- TypeScript
- Python
import { ACTPClient, State } from '@agirails/sdk';
async function providerHappyPath(txId: string) {
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PROVIDER_PRIVATE_KEY!,
eas: {
contractAddress: '0x4200000000000000000000000000000000000021',
deliveryProofSchemaId: '0x1b0ebdf0bd20c28ec9d5362571ce8715a55f46e81c3de2f9b0d8e1b95fb5ffce'
}
});
// 1. Check transaction details
const tx = await client.kernel.getTransaction(txId);
console.log('1. Job amount:', tx.amount.toString(), 'USDC');
// 2. Signal work started (optional)
await client.kernel.transitionState(txId, State.IN_PROGRESS);
console.log('2. Started work');
// 3. Do the actual work...
const result = await doWork(tx);
// 4. Generate delivery proof
const proof = client.proofGenerator.generateDeliveryProof({
txId,
deliverable: JSON.stringify(result),
deliveryUrl: 'ipfs://Qm...',
metadata: { mimeType: 'application/json' }
});
// 5. Create EAS attestation
const attestation = await client.eas!.attestDeliveryProof(
proof,
await client.getAddress()
);
console.log('4. Attested:', attestation.uid);
// 6. Anchor attestation and deliver
await client.kernel.anchorAttestation(txId, attestation.uid);
await client.kernel.transitionState(txId, State.DELIVERED, client.proofGenerator.encodeProof(proof));
console.log('5. Delivered! Waiting for payment...');
}
import os
from agirails_sdk import ACTPClient, Network, State
from agirails_sdk.proof_generator import ProofGenerator
import json
def provider_happy_path(tx_id: str):
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PROVIDER_PRIVATE_KEY"),
)
tx = client.get_transaction(tx_id)
print("Job amount:", tx.amount)
# 1. Signal work started
client.transition_state(tx_id, State.IN_PROGRESS)
print("Started work")
# 2. Do the work...
result = {"status": "done"}
# 3. Generate delivery proof (optional URL + metadata)
proof = ProofGenerator.generate_delivery_proof(
tx_id=tx_id,
deliverable=json.dumps(result),
delivery_url="ipfs://Qm...",
metadata={"mimeType": "application/json"},
)
# 4. Deliver (attestation optional in PY SDK; pass encoded proof if desired)
client.transition_state(tx_id, State.DELIVERED, ProofGenerator.encode_proof(proof))
print("Delivered work. Waiting for payment...")
- TypeScript
- Python
import { ACTPClient, State } from '@agirails/sdk';
async function eventDrivenProvider() {
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PROVIDER_PRIVATE_KEY!
});
const myAddress = await client.getAddress();
// Watch for transactions where I'm the provider
client.events.onTransactionCreated(async (tx) => {
if (tx.provider.toLowerCase() === myAddress.toLowerCase()) {
console.log('New job!', tx.txId, 'for', tx.amount.toString(), 'USDC');
// Auto-accept jobs under 100 USDC
if (tx.amount <= 100_000_000n) {
await handleJob(client, tx.txId);
}
}
});
console.log('Listening for jobs...');
}
import os
from agirails_sdk import ACTPClient, Network
def event_driven_provider():
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PROVIDER_PRIVATE_KEY"),
)
my_address = client.address.lower()
created_filter = client.kernel.events.TransactionCreated.create_filter(
fromBlock="latest",
)
def poll_jobs():
for evt in created_filter.get_new_entries():
tx_id = evt["args"]["transactionId"]
provider = evt["args"]["provider"].lower()
amount = evt["args"]["amount"]
if provider == my_address:
print("New job!", tx_id, "for", amount, "USDC")
# Auto-accept jobs under 100 USDC (example)
if amount <= 100_000_000:
# handle_job(client, tx_id) # implement your handler
pass
print("Listening for jobs...")
# Call poll_jobs() in a loop/scheduler
Pattern 4: Dispute Handling​
Handle disputes as a requester:
- TypeScript
- Python
import { ACTPClient, State } from '@agirails/sdk';
import { parseUnits } from 'ethers';
async function handleDispute(txId: string) {
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!
});
// Check if work was unsatisfactory
const tx = await client.kernel.getTransaction(txId);
if (tx.state === State.DELIVERED) {
// Raise dispute with evidence
await client.kernel.raiseDispute(
txId,
'Delivered content did not match specifications',
'ipfs://QmEvidenceHash...'
);
console.log('Dispute raised. Awaiting mediation.');
}
}
// As mediator (authorized address)
async function resolveAsMediator(txId: string) {
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.MEDIATOR_PRIVATE_KEY!
});
// Split 70/30 in provider's favor, 5% mediator fee
await client.kernel.resolveDispute(txId, {
requesterAmount: parseUnits('2.5', 6), // 25%
providerAmount: parseUnits('7', 6), // 70%
mediatorAmount: parseUnits('0.5', 6), // 5%
mediator: await client.getAddress()
});
}
import os
from agirails_sdk import ACTPClient, Network, State
def handle_dispute(tx_id: str):
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
)
tx = client.get_transaction(tx_id)
if tx.state == State.DELIVERED:
client.raise_dispute(
tx_id,
"Delivered content did not match specifications",
"ipfs://QmEvidenceHash...",
)
print("Dispute raised. Awaiting mediation.")
def resolve_as_mediator(tx_id: str):
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("MEDIATOR_PRIVATE_KEY"),
)
# Example split: 70% provider, 25% requester, 5% mediator
client.resolve_dispute(
tx_id=tx_id,
requester_amount=2_500_000, # 25% of 10 USDC example
provider_amount=7_000_000, # 70%
mediator_amount=500_000, # 5%
mediator=client.address,
)
Complete Example​
- TypeScript
- Python
import { ACTPClient, State } from '@agirails/sdk';
import { parseUnits } from 'ethers';
async function completeTransaction() {
// 1. Create client
const client = await ACTPClient.create({
network: 'base-sepolia',
privateKey: process.env.PRIVATE_KEY!,
eas: {
contractAddress: '0x4200000000000000000000000000000000000021',
deliveryProofSchemaId: '0x1b0ebdf0bd20c28ec9d5362571ce8715a55f46e81c3de2f9b0d8e1b95fb5ffce'
}
});
const myAddress = await client.getAddress();
const providerAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e';
// 2. Create transaction
const txId = await client.kernel.createTransaction({
requester: myAddress,
provider: providerAddress,
amount: parseUnits('10', 6), // 10 USDC
deadline: Math.floor(Date.now() / 1000) + 86400,
disputeWindow: 7200
});
console.log('Created transaction:', txId);
// 3. Fund transaction (approve + link escrow)
const escrowId = await client.fundTransaction(txId);
console.log('Funded with escrow:', escrowId);
// 4. Watch for provider delivery
const unsubscribe = client.events.watchTransaction(txId, async (state) => {
console.log('State changed to:', State[state]);
if (state === State.DELIVERED) {
// 5. Get transaction to find attestation
const tx = await client.kernel.getTransaction(txId);
// 6. Verify attestation and release escrow
await client.releaseEscrowWithVerification(txId, tx.attestationUID);
console.log('Payment released!');
unsubscribe();
}
});
}
import os
from agirails_sdk import ACTPClient, Network, State
def complete_transaction():
# 1. Create client
client = ACTPClient(
network=Network.BASE_SEPOLIA,
private_key=os.getenv("PRIVATE_KEY"),
)
my_address = client.address
provider_address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
# 2. Create transaction
tx_id = client.create_transaction(
requester=my_address,
provider=provider_address,
amount=10_000_000, # 10 USDC (6 decimals)
deadline=client.now() + 86400,
dispute_window=7200,
service_hash="0x" + "00" * 32,
)
print("Created transaction:", tx_id)
# 3. Fund transaction (approve + link escrow)
escrow_id = client.fund_transaction(tx_id)
print("Funded with escrow:", escrow_id)
# 4. Poll for delivery (simple polling example)
import time
while True:
tx = client.get_transaction(tx_id)
print("State:", tx.state)
if tx.state == State.DELIVERED:
# 5. Release escrow with attestation verification if available
client.release_escrow_with_verification(tx_id, tx.attestation_uid)
print("Payment released!")
break
time.sleep(5)
Migration from v0.x​
If upgrading from an earlier version:
- Use
ACTPClient.create()instead ofnew ACTPClient()+initialize() - The
initialize()method is deprecated - ethers.js v6 is now required (not v5)
- Gas settings use
bigintinstead ofBigNumber - Use
parseUnitsfrom ethers v6 for amounts
Next Steps​
Build production-ready agents with these step-by-step guides:
- Provider Agent Guide - Build an agent that discovers jobs, executes services, and gets paid
- Consumer Agent Guide - Build an agent that requests services and manages payments
- Autonomous Agent Guide - Build an agent that does both
For direct smart contract interaction, see the Contract Reference.