@ar.io/sdk
Version:
[](https://codecov.io/gh/ar-io/ar-io-sdk)
169 lines (168 loc) • 6.71 kB
JavaScript
/**
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { connect } from '@permaweb/aoconnect';
import { EventEmitter } from 'eventemitter3';
import { pLimit } from 'plimit-lit';
import { ANTRegistry } from '../common/ant-registry.js';
import { ANT } from '../common/ant.js';
import { AOProcess } from '../common/index.js';
import { ARIO } from '../common/io.js';
import { Logger } from '../common/logger.js';
import { ARIO_MAINNET_PROCESS_ID } from '../constants.js';
/**
* @beta This API is in beta and may change in the future.
*/
export const getANTProcessesOwnedByWallet = async ({ address, registry = ANTRegistry.init(), }) => {
const res = await registry.accessControlList({ address });
return [...new Set([...res.Owned, ...res.Controlled])];
};
function timeout(ms, promise) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error('Timeout'));
}, ms);
promise
.then((value) => {
clearTimeout(timer);
resolve(value);
})
.catch((err) => {
clearTimeout(timer);
reject(err);
});
});
}
export class ArNSEventEmitter extends EventEmitter {
contract;
timeoutMs; // timeout for each request to 3 seconds
throttle;
logger;
strict;
antAoClient;
constructor({ contract = ARIO.init({
processId: ARIO_MAINNET_PROCESS_ID,
}), timeoutMs = 60_000, concurrency = 30, logger = Logger.default, strict = false, antAoClient = connect({
MODE: 'legacy',
}), } = {}) {
super();
this.contract = contract;
this.timeoutMs = timeoutMs;
this.throttle = pLimit(concurrency);
this.logger = logger;
this.strict = strict;
this.antAoClient = antAoClient;
}
async fetchProcessesOwnedByWallet({ address, pageSize, antRegistry = ANTRegistry.init(), }) {
const uniqueContractProcessIds = {};
const antIdRes = await antRegistry.accessControlList({ address });
const antIds = new Set([...antIdRes.Owned, ...antIdRes.Controlled]);
await timeout(this.timeoutMs, fetchAllArNSRecords({ contract: this.contract, emitter: this, pageSize }))
.catch((e) => {
this.emit('error', `Error getting ArNS records: ${e}`);
this.logger.error(`Error getting ArNS records`, {
message: e?.message,
stack: e?.stack,
});
return {};
})
.then((records) => {
Object.entries(records).forEach(([name, arnsRecord]) => {
if (antIds.has(arnsRecord.processId)) {
if (uniqueContractProcessIds[arnsRecord.processId] == undefined) {
uniqueContractProcessIds[arnsRecord.processId] = {
state: undefined,
names: {},
};
}
uniqueContractProcessIds[arnsRecord.processId].names[name] =
arnsRecord;
}
});
});
const idCount = Object.keys(uniqueContractProcessIds).length;
this.emit('progress', 0, idCount);
// check the contract owner and controllers
await Promise.all(Object.keys(uniqueContractProcessIds).map(async (processId, i) => this.throttle(async () => {
if (uniqueContractProcessIds[processId].state !== undefined) {
this.emit('progress', i + 1, idCount);
return;
}
const ant = ANT.init({
process: new AOProcess({
processId,
ao: this.antAoClient,
}),
strict: this.strict,
});
const state = (await timeout(this.timeoutMs, ant.getState()).catch((e) => {
this.emit('error', `Error getting state for process ${processId}: ${e}`);
return undefined;
}));
if (state?.Owner === address ||
state?.Controllers.includes(address)) {
uniqueContractProcessIds[processId].state = state;
this.emit('process', processId, uniqueContractProcessIds[processId]);
}
this.emit('progress', i + 1, idCount);
})));
this.emit('end', uniqueContractProcessIds);
}
}
export const fetchAllArNSRecords = async ({ contract = ARIO.init({
processId: ARIO_MAINNET_PROCESS_ID,
}), emitter, logger = Logger.default, pageSize = 1000, }) => {
let cursor;
const startTimestamp = Date.now();
const records = {};
do {
const pageResult = await contract
.getArNSRecords({ cursor, limit: pageSize })
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((e) => {
logger?.error(`Error getting ArNS records`, {
message: e?.message,
stack: e?.stack,
});
emitter?.emit('arns:error', `Error getting ArNS records: ${e}`);
return undefined;
});
if (!pageResult) {
return {};
}
pageResult.items.forEach((record) => {
const { name, ...recordDetails } = record;
records[name] = recordDetails;
});
logger.debug('Fetched page of ArNS records', {
totalRecordCount: pageResult.totalItems,
fetchedRecordCount: Object.keys(records).length,
cursor: pageResult.nextCursor,
});
emitter?.emit('arns:pageLoaded', {
totalRecordCount: pageResult.totalItems,
fetchedRecordCount: Object.keys(records).length,
records: pageResult.items,
cursor: pageResult.nextCursor,
});
cursor = pageResult.nextCursor;
} while (cursor !== undefined);
emitter?.emit('arns:end', records);
logger.debug('Fetched all ArNS records', {
totalRecordCount: Object.keys(records).length,
durationMs: Date.now() - startTimestamp,
});
return records;
};