@agentic-trust/8004-ext-sdk
Version:
ERC-8004 Agentic Trust SDK - A TypeScript SDK for managing AI agents with ENS integration, identity management, and reputation systems
637 lines (621 loc) • 25.7 kB
JavaScript
import { createPublicClient, http, encodeFunctionData, namehash } from 'viem';
import { AIAgentENSClient } from './AIAgentENSClient';
export class AIAgentL2ENSDurenClient extends AIAgentENSClient {
constructor(chain, rpcUrl, adapter, ensRegistryAddress, ensResolverAddress, identityRegistryAddress) {
super(chain, rpcUrl, adapter, '0x119bFf40969bFBe0438c3f72f3855958E8E0d30c', '0x119bFf40969bFBe0438c3f72f3855958E8E0d30c', identityRegistryAddress);
//ensRegistryAddress = '0xf584057f3e9ecd550b52a86f84dfeb9f928e003f' as `0x${string}`;
//ensResolverAddress = '0xf584057f3e9ecd550b52a86f84dfeb9f928e003f' as `0x${string}`;
}
/**
* Override to ensure L2 client always returns true for isL2()
*/
isL2() {
return true; // This is always an L2 client
}
/**
* Override to ensure L2 client always returns false for isL1()
*/
isL1() {
return false; // This is never an L1 client
}
/**
* Override to ensure L2 client always returns 'L2'
*/
getChainType() {
return 'L2';
}
async getAgentUrlByName(name) {
console.info("AIAgentL2ENSDurenClient.getAgentUrlByName: ", name);
try {
// Calculate namehash for the subdomain
const node = namehash(name);
console.info("AIAgentL2ENSDurenClient.getAgentUrlByName: node", node);
// Use direct resolver call to get URL text record (equivalent to cast call)
const resolverAddress = this.getEnsResolverAddress();
// ENS Resolver ABI for text function
const resolverAbi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"internalType": "string",
"name": "key",
"type": "string"
}
],
"name": "text",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
];
// Create public client for reading
const publicClient = // @ts-ignore - viem version compatibility issue
createPublicClient({
chain: this.chain,
transport: http(this.rpcUrl)
});
// Call the resolver directly to get URL text record
const url = await // @ts-ignore - viem version compatibility issue
publicClient.readContract({
address: resolverAddress,
abi: resolverAbi,
functionName: 'text',
args: [node, 'url']
});
console.info("AIAgentL2ENSDurenClient.getAgentUrlByName: resolved url", url);
// Return null if URL is empty
if (!url || url.trim() === '') {
return null;
}
return url;
}
catch (error) {
console.error('Error resolving URL for name:', name, error);
return null;
}
}
async getAgentAccountByName(name) {
console.info("AIAgentL2ENSDurenClient.getAgentAccountByName: ", name);
try {
// Calculate namehash for the subdomain
const node = namehash(name);
console.info(".....node from hash: ", name, " is: ", node);
//const node = '0x6ea6fadc0faff80d2349984bfc18c82b246ba9e8ba697f0356956a4f1e6b2b29' as `0x${string}`;
console.info("AIAgentL2ENSDurenClient.getAgentAccountByName: node", node);
// TEST: Check if NFT exists but may not have address set
console.info("********************* TEST: Checking if NFT exists for name:", name);
// First check if the name exists in the ENS registry (has an owner)
const ensRegistryAddress = this.getEnsRegistryAddress();
const ensRegistryAbi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
];
console.info("********************* TEST: Public client chain", this.chain);
console.info("********************* TEST: Public client rpcUrl", this.rpcUrl);
const publicClient = // @ts-ignore - viem version compatibility issue
createPublicClient({
chain: this.chain,
transport: http(this.rpcUrl)
});
try {
const owner = await // @ts-ignore - viem version compatibility issue
publicClient.readContract({
address: ensRegistryAddress,
abi: ensRegistryAbi,
functionName: 'owner',
args: [node]
});
console.info("********************* TEST: ENS Registry owner for", name, ":", owner);
if (owner && owner !== '0x0000000000000000000000000000000000000000') {
console.info("********************* TEST: NFT EXISTS - Name has owner:", owner);
}
else {
console.info("********************* TEST: NFT DOES NOT EXIST - No owner found");
}
}
catch (registryError) {
console.error("********************* TEST: Error checking ENS registry:", registryError);
}
// Use direct resolver call to get address (equivalent to cast call)
const resolverAddress = this.getEnsResolverAddress();
// ENS Resolver ABI for addr function
const resolverAbi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "addr",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
];
/*
// TEST: Check resolver status
console.info("********************* TEST: Checking resolver for name:", name);
// Check if resolver is set for this node
const registryResolverAbi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "resolver",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
] as const;
try {
const resolver = await // @ts-ignore - viem version compatibility issue
publicClient.readContract({
address: ensRegistryAddress,
abi: registryResolverAbi,
functionName: 'resolver',
args: [node]
});
console.info("********************* TEST: Resolver for", name, ":", resolver);
if (resolver && resolver !== '0x0000000000000000000000000000000000000000') {
console.info("********************* TEST: RESOLVER EXISTS - Name has resolver:", resolver);
} else {
console.info("********************* TEST: NO RESOLVER - Name has no resolver set");
}
} catch (resolverError) {
console.error("********************* TEST: Error checking resolver:", resolverError);
}
*/
// Call the resolver directly to get address
const addressResolverAbi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "addr",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
];
const address = await // @ts-ignore - viem version compatibility issue
publicClient.readContract({
address: resolverAddress,
abi: addressResolverAbi,
functionName: 'addr',
args: [node]
});
console.info("AIAgentL2ENSDurenClient.getAgentAccountByName: resolved address", address);
console.info("********************* TEST: Address resolution result:", address);
// Return null if address is zero address
if (!address || address === '0x0000000000000000000000000000000000000000') {
return null;
}
return address;
}
catch (error) {
console.error('Error resolving address for name:', name, error);
return null;
}
}
/**
* Note: getAgentEoaByAgentAccount is not a method of AIAgentENSClient
* This method is actually in AIAgentIdentityClient, so we don't need to override it here.
* The ownership detection logic is handled in the UI layer (AddAgentModal.tsx)
*/
/**
* Override hasAgentNameOwner to use L2Registrar available() function
*/
async hasAgentNameOwner(orgName, agentName) {
console.info("AIAgentL2ENSDurenClient.hasAgentNameOwner");
const clean = (s) => (s || '').trim().toLowerCase();
const parent = clean(orgName);
const label = clean(agentName).replace(/\s+/g, '-');
console.info("AIAgentL2ENSDurenClient.hasAgentNameOwner: label", label);
console.info("AIAgentL2ENSDurenClient.hasAgentNameOwner: parent", parent);
const fullSubname = `${label}.${parent}`;
console.info("AIAgentL2ENSDurenClient.hasAgentNameOwner: fullSubname", fullSubname);
// Use L2Registrar contract address (same as registerSubdomain)
const l2RegistrarAddress = "0x68CAd072571E8bea1DA9e5C071367Aa6ddC8F37F";
const l2RegistrarAbi = [
{
"inputs": [
{
"internalType": "string",
"name": "label",
"type": "string"
}
],
"name": "available",
"outputs": [
{
"internalType": "bool",
"name": "available",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
];
const publicClient = // @ts-ignore - viem version compatibility issue
createPublicClient({
chain: this.chain,
transport: http(this.rpcUrl)
});
try {
// Call registrar.available(label) - returns true if available, false if taken
const isAvailable = await // @ts-ignore - viem version compatibility issue
publicClient.readContract({
address: l2RegistrarAddress,
abi: l2RegistrarAbi,
functionName: 'available',
args: [label]
});
// If not available, then it has an owner
const hasOwner = !isAvailable;
console.info(`AIAgentL2ENSDurenClient.hasAgentNameOwner: "${fullSubname}" (label: "${label}") ${hasOwner ? 'HAS owner' : 'has NO owner'} (available: ${isAvailable})`);
return hasOwner;
}
catch (error) {
console.error('Error checking agent name owner via registrar:', error);
return false;
}
}
async prepareAddAgentNameToOrgCalls(params) {
console.log("AIAgentL2ENSDurenClient.prepareAddAgentNameToOrgCalls");
console.log("orgName: ", params.orgName);
console.log("agentName: ", params.agentName);
console.log("agentAddress: ", params.agentAddress);
console.log("agentUrl: ", params.agentUrl);
const clean = (s) => (s || '').trim().toLowerCase();
const parent = clean(params.orgName) + ".eth";
const label = clean(params.agentName).replace(/\s+/g, '-');
const fullSubname = `${label}.${parent}`;
const agentAddress = params.agentAddress;
const agentUrl = params.agentUrl;
const chainName = this.chain.name.toLowerCase().replace(/\s+/g, '-');
console.info("parent: ", parent);
console.info("label: ", label);
console.info("fullSubname: ", fullSubname);
console.info("agentAddress: ", agentAddress);
console.info("chainName: ", chainName);
console.info("agentUrl: ", agentUrl);
const calls = [];
try {
// Step 1: Register the subdomain with L2Registrar
const registrationCall = await this.registerSubdomain(label, agentAddress);
// Add registration call
calls.push(...registrationCall.calls);
/*
// Step 2: Set address records for the subdomain
const chainId = (this as any).chain.id;
// Set Base Sepolia address record (coinType 2147568164)
if (chainId === 84532) { // Base Sepolia
const baseAddrCall = await this.setResolverAddrRecordDirect(
fullSubname,
2147568164, // Base Sepolia coin type
agentAddress
);
calls.push(baseAddrCall);
}
// Set Ethereum address record (coinType 60) for cross-chain compatibility
const ethAddrCall = await this.setResolverAddrRecordDirect(
fullSubname,
60, // Ethereum coin type
agentAddress
);
calls.push(ethAddrCall);
// Step 3: Set resolver records for the subdomain
const textRecords = [
{ key: 'name', value: label },
{ key: 'url', value: agentUrl },
{ key: 'description', value: `Agent: ${label}` },
{ key: 'chain', value: chainName },
{ key: 'agent-account', value: agentAddress },
];
for (const record of textRecords) {
const recordCall = await this.setResolverTextRecordDirect(
fullSubname,
record.key,
record.value,
);
calls.push(recordCall);
}
console.info("Generated calls count: ", calls.length);
console.info("Calls: ", calls);
*/
}
catch (error) {
console.error("Error preparing agent name calls:", error);
throw error;
}
return { calls };
}
async prepareAddAgentInfoCalls(params) {
console.log("AIAgentL2ENSDurenClient.prepareAddAgentNameToOrgCalls");
console.log("orgName: ", params.orgName);
console.log("agentName: ", params.agentName);
console.log("agentAddress: ", params.agentAddress);
console.log("agentUrl: ", params.agentUrl);
const clean = (s) => (s || '').trim().toLowerCase();
const parent = clean(params.orgName) + ".eth";
const label = clean(params.agentName).replace(/\s+/g, '-');
const fullSubname = `${label}.${parent}`;
const agentAddress = params.agentAddress;
const agentUrl = params.agentUrl;
const chainName = this.chain.name.toLowerCase().replace(/\s+/g, '-');
console.info("parent: ", parent);
console.info("label: ", label);
console.info("fullSubname: ", fullSubname);
console.info("agentAddress: ", agentAddress);
console.info("chainName: ", chainName);
console.info("agentUrl: ", agentUrl);
const calls = [];
try {
// Step 2: Set address records for the subdomain
const chainId = this.chain.id;
// Set Base Sepolia address record (coinType 2147568164)
if (chainId === 84532) { // Base Sepolia
const baseAddrCall = await this.setResolverAddrRecordDirect(fullSubname, 2147568164, // Base Sepolia coin type
agentAddress);
calls.push(baseAddrCall);
}
/*
// Set Ethereum address record (coinType 60) for cross-chain compatibility
const ethAddrCall = await this.setResolverAddrRecordDirect(
fullSubname,
60, // Ethereum coin type
agentAddress
);
calls.push(ethAddrCall);
*/
// Step 3: Set resolver records for the subdomain
const textRecords = [
//{ key: 'name', value: label },
{ key: 'url', value: agentUrl },
//{ key: 'description', value: `Agent: ${label}` },
//{ key: 'chain', value: chainName },
//{ key: 'agent-account', value: agentAddress },
];
// Add description if provided
if (params.agentDescription && params.agentDescription.trim() !== '') {
textRecords.push({ key: 'description', value: params.agentDescription.trim() });
}
for (const record of textRecords) {
const recordCall = await this.setResolverTextRecordDirect(fullSubname, record.key, record.value);
calls.push(recordCall);
}
console.info("Generated calls count: ", calls.length);
console.info("Calls: ", calls);
}
catch (error) {
console.error("Error preparing agent name calls:", error);
throw error;
}
return { calls };
}
async prepareSetNameUriCalls(name, uri) {
const calls = [];
return { calls };
}
/**
* Register subdomain using L2Registrar contract (Base Sepolia specific)
*/
async registerSubdomain(subdomain, owner) {
console.log("AIAgentL2ENSDurenClient.registerSubdomain");
console.log("subdomain:", subdomain);
console.log("owner:", owner);
const calls = [];
// L2Registrar ABI - 2-parameter register function
const l2RegistrarAbi = [
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "register",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
// Use L2Registrar contract address for Base Sepolia
const l2RegistrarAddress = "0x68CAd072571E8bea1DA9e5C071367Aa6ddC8F37F";
// Register subdomain using L2Registrar (equivalent to your cast command)
const registerData = encodeFunctionData({
abi: l2RegistrarAbi,
functionName: 'register',
args: [subdomain, owner]
});
calls.push({
to: l2RegistrarAddress,
data: registerData,
value: 0n
});
return { calls };
}
/**
* Direct chain call for setting resolver records
*/
async setResolverTextRecordDirect(name, key, value) {
console.log("AIAgentL2ENSDurenClient.setResolverTextRecordDirect");
console.log("name:", name);
console.log("key:", key);
console.log("value:", value);
const resolverAddress = this.getEnsResolverAddress();
// ENS Resolver ABI for setText
const resolverAbi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"internalType": "string",
"name": "key",
"type": "string"
},
{
"internalType": "string",
"name": "value",
"type": "string"
}
],
"name": "setText",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
// Calculate namehash for the subdomain
const node = namehash(name);
// Encode the setText function call
const data = encodeFunctionData({
abi: resolverAbi,
functionName: 'setText',
args: [node, key, value]
});
return {
to: resolverAddress,
data
};
}
/**
* Direct chain call for setting resolver address records
* Equivalent to: cast send resolver "setAddr(bytes32,uint256,bytes)" $NODE coinType encodedAddress
*/
async setResolverAddrRecordDirect(name, coinType, address) {
console.log("AIAgentL2ENSDurenClient.setResolverAddrRecordDirect");
console.log("name:", name);
console.log("coinType:", coinType);
console.log("address:", address);
const resolverAddress = this.getEnsResolverAddress();
// ENS Resolver ABI for setAddr
const resolverAbi = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "coinType",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "a",
"type": "bytes"
}
],
"name": "setAddr",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
// Calculate namehash for the subdomain
const node = namehash(name);
// Encode the address according to the coin type
// For Base Sepolia (coinType 2147568164), we need to encode as bytes
let encodedAddress;
if (coinType === 2147568164) {
// Base Sepolia coin type - encode as bytes
// Equivalent to: $(cast abi-encode "f(address)" 0xYourBaseAddress)
encodedAddress = encodeFunctionData({
abi: [{ "inputs": [{ "internalType": "address", "name": "addr", "type": "address" }], "name": "f", "outputs": [], "stateMutability": "nonpayable", "type": "function" }],
functionName: 'f',
args: [address]
});
}
else if (coinType === 60) {
// Ethereum coin type - encode as 20-byte address
encodedAddress = address.padEnd(66, '0');
}
else {
// Generic encoding for other coin types
encodedAddress = address.padEnd(66, '0');
}
console.log("node:", node);
console.log("encodedAddress:", encodedAddress);
// Encode the setAddr function call
const data = encodeFunctionData({
abi: resolverAbi,
functionName: 'setAddr',
args: [node, BigInt(coinType), encodedAddress]
});
return {
to: resolverAddress,
data
};
}
}
//# sourceMappingURL=AIAgentL2ENSDurenClient.js.map