@ar.io/sdk
Version:
[](https://codecov.io/gh/ar-io/ar-io-sdk)
257 lines (256 loc) • 9.67 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultANTLogoId = exports.defaultTargetManifestId = void 0;
exports.spawnANT = spawnANT;
exports.evolveANT = evolveANT;
exports.isAoSigner = isAoSigner;
exports.createAoSigner = createAoSigner;
exports.initANTStateForAddress = initANTStateForAddress;
exports.parseAoEpochData = parseAoEpochData;
exports.errorMessageFromOutput = errorMessageFromOutput;
exports.removeUnicodeFromError = removeUnicodeFromError;
/**
* 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.
*/
const arbundles_1 = require("@dha-team/arbundles");
const aoconnect_1 = require("@permaweb/aoconnect");
const zod_1 = require("zod");
const arweave_js_1 = require("../common/arweave.js");
const index_js_1 = require("../common/index.js");
const constants_js_1 = require("../constants.js");
const ant_js_1 = require("../types/ant.js");
const schema_js_1 = require("./schema.js");
async function spawnANT({ signer, module = constants_js_1.AOS_MODULE_ID, ao = (0, aoconnect_1.connect)({
MODE: 'legacy',
}), scheduler = constants_js_1.DEFAULT_SCHEDULER_ID, state, antRegistryId = constants_js_1.ANT_REGISTRY_ID, logger = index_js_1.Logger.default, authority = constants_js_1.AO_AUTHORITY, }) {
// TODO: use On-Boot data handler for bootstrapping state instead of initialize-state
if (state) {
(0, schema_js_1.parseSchemaResult)(ant_js_1.SpawnANTStateSchema, state);
}
const processId = await ao.spawn({
module,
scheduler,
signer,
data: state ? JSON.stringify(state) : undefined,
tags: [
// Required for AOS to initialize the authorities table
{
name: 'Authority',
value: authority,
},
{
name: 'ANT-Registry-Id',
value: antRegistryId,
},
],
});
let bootRes;
let attempts = 0;
while (attempts < 5 && bootRes === undefined) {
try {
bootRes = await ao.result({
process: processId,
message: processId,
});
break;
}
catch (error) {
logger.debug('Retrying ANT boot result fetch', {
processId,
module,
scheduler,
attempts,
error,
});
attempts++;
await new Promise((resolve) => setTimeout(resolve, 1000 * attempts ** 2));
}
}
if (bootRes === undefined ||
bootRes.Messages?.some((m) => m?.Tags?.some((t) => t.value === 'Invalid-Boot-Notice'))) {
if (bootRes === undefined) {
// …
throw new Error('Failed to get boot result');
}
const bootError = errorMessageFromOutput(bootRes);
logger.error('ANT failed to boot correctly', {
processId,
module,
scheduler,
bootRes,
bootError,
});
throw new Error(`ANT failed to boot correctly: ${bootError}`);
}
logger.debug(`Spawned ANT`, {
processId,
module,
scheduler,
});
return processId;
}
async function evolveANT({ signer, processId, luaCodeTxId = constants_js_1.ANT_LUA_ID, ao = (0, aoconnect_1.connect)({
MODE: 'legacy',
}), logger = index_js_1.Logger.default, arweave = arweave_js_1.defaultArweave, }) {
const aosClient = new index_js_1.AOProcess({
processId,
ao,
logger,
});
//TODO: cache locally and only fetch if not cached
// We do not use arweave to get the data because it may throw on l2 tx data
const { api: { host, port, protocol }, } = arweave.getConfig();
const luaString = await fetch(`${protocol}://${host}:${port}/${luaCodeTxId}`).then((res) => res.text());
const { id: evolveMsgId } = await aosClient.send({
tags: [
{ name: 'Action', value: 'Eval' },
{ name: 'App-Name', value: 'ArNS-ANT' },
{ name: 'Source-Code-TX-ID', value: luaCodeTxId },
],
data: luaString,
signer,
});
logger.debug(`Evolved ANT`, {
processId,
luaCodeTxId,
evalMsgId: evolveMsgId,
});
return evolveMsgId;
}
function isAoSigner(value) {
const TagSchema = zod_1.z.object({
name: zod_1.z.string(),
value: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]),
});
const AoSignerSchema = zod_1.z
.function()
.args(zod_1.z.object({
data: zod_1.z.union([zod_1.z.string(), zod_1.z.instanceof(Buffer)]),
tags: zod_1.z.array(TagSchema).optional(),
target: zod_1.z.string().optional(),
anchor: zod_1.z.string().optional(),
}))
.returns(zod_1.z.promise(zod_1.z.object({
id: zod_1.z.string(),
raw: zod_1.z.instanceof(ArrayBuffer),
})));
try {
AoSignerSchema.parse(value);
return true;
}
catch {
return false;
}
}
function createAoSigner(signer) {
if (isAoSigner(signer)) {
return signer;
}
if (!('publicKey' in signer)) {
return (0, aoconnect_1.createDataItemSigner)(signer);
}
const aoSigner = async ({ data, tags, target, anchor }) => {
// ensure appropriate permissions are granted with injected signers.
if (signer.publicKey === undefined &&
'setPublicKey' in signer &&
typeof signer.setPublicKey === 'function') {
await signer.setPublicKey();
}
if (signer instanceof arbundles_1.ArconnectSigner) {
// Sign using Arconnect signDataItem API
const signedDataItem = await signer['signer'].signDataItem({
data,
tags,
target,
anchor,
});
const dataItem = new arbundles_1.DataItem(Buffer.from(signedDataItem));
return {
id: await dataItem.id,
raw: await dataItem.getRaw(),
};
}
const dataItem = (0, arbundles_1.createData)(data, signer, { tags, target, anchor });
const signedData = dataItem.sign(signer).then(async () => ({
id: await dataItem.id,
raw: await dataItem.getRaw(),
}));
return signedData;
};
// eslint-disable-next-line
// @ts-ignore Buffer vs ArrayBuffer type mismatch
return aoSigner;
}
exports.defaultTargetManifestId = '-k7t8xMoB8hW482609Z9F4bTFMC3MnuW8bTvTyT8pFI';
exports.defaultANTLogoId = 'Sie_26dvgyok0PZD_-iQAFOhOd5YxDTkczOLoqTTL_A';
function initANTStateForAddress({ owner, targetId, ttlSeconds = 3600, keywords = [], controllers = [], description = '', ticker = 'aos', name = 'ANT', logo = exports.defaultANTLogoId, }) {
return {
ticker,
name,
description,
keywords,
owner,
controllers: [owner, ...controllers],
balances: { [owner]: 1 },
records: {
['@']: {
transactionId: targetId ?? exports.defaultTargetManifestId.toString(),
ttlSeconds,
},
},
logo,
};
}
/**
* Uses zod schema to parse the epoch data
*/
function parseAoEpochData(value) {
const epochDataSchema = zod_1.z.object({
startTimestamp: zod_1.z.number(),
startHeight: zod_1.z.number(),
distributions: zod_1.z.any(), // TODO: add full distributed object type
endTimestamp: zod_1.z.number(),
prescribedObservers: zod_1.z.any(),
prescribedNames: zod_1.z.array(zod_1.z.string()),
observations: zod_1.z.any(),
epochIndex: zod_1.z.number(),
});
return epochDataSchema.parse(value);
}
function errorMessageFromOutput(output) {
const errorData = output.Error;
// Attempt to extract error details from Messages.Tags if Error is undefined
const error = errorData ??
output.Messages?.[0]?.Tags?.find((tag) => tag.name === 'Error')?.value;
if (error !== undefined) {
// Consolidated regex to match and extract line number and AO error message or Error Tags
const match = error.match(/\[string "aos"]:(\d+):\s*(.+)/);
if (match) {
const [, lineNumber, errorMessage] = match;
const cleanError = removeUnicodeFromError(errorMessage);
return `${cleanError.trim()} (line ${lineNumber.trim()})`.trim();
}
// With no match, just remove unicode
return removeUnicodeFromError(error);
}
return undefined;
}
function removeUnicodeFromError(error) {
//The regular expression /[\u001b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g is designed to match ANSI escape codes used for terminal formatting. These are sequences that begin with \u001b (ESC character) and are often followed by [ and control codes.
const ESC = String.fromCharCode(27); // Represents '\u001b' or '\x1b'
return error
.replace(new RegExp(`${ESC}[\\[\\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]`, 'g'), '')
.trim();
}
;