Skip to content

Commit

Permalink
Merge pull request #108 from ar-io/io-ao-contract
Browse files Browse the repository at this point in the history
feat(ao): io/ao contract classes
  • Loading branch information
dtfiedler authored Jun 7, 2024
2 parents 9ffe14b + 4a4026f commit 19e6f7d
Show file tree
Hide file tree
Showing 14 changed files with 1,323 additions and 43 deletions.
30 changes: 10 additions & 20 deletions examples/esm/index.mjs
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
import { ARNS_TESTNET_REGISTRY_TX, ArIO } from '@ar.io/sdk';
import { IO, ioDevnetProcessId } from '@ar.io/sdk';

(async () => {
const arIO = ArIO.init({
contractTxId: ARNS_TESTNET_REGISTRY_TX,
const arIO = IO.init({
processId: ioDevnetProcessId,
});
// testnet gateways
// devnet gateways
const testnetGateways = await arIO.getGateways();
const protocolBalance = await arIO.getBalance({
address: ARNS_TESTNET_REGISTRY_TX,
address: ioDevnetProcessId,
});
const ardriveRecord = await arIO.getArNSRecord({ domain: 'ardrive' });
const ardriveRecord = await arIO.getArNSRecord({ name: 'ardrive' });
const allRecords = await arIO.getArNSRecords();
const oldEpoch = await arIO.getEpoch({
blockHeight: 1382230,
});
const epoch = await arIO.getCurrentEpoch();
const observations = await arIO.getObservations();
const observation = await arIO.getObservations({ epochStartHeight: 1350700 });
const distributions = await arIO.getDistributions();

const observations = await arIO.getObservations({ epochIndex: 19879 });
const distributions = await arIO.getDistributions({ epochIndex: 19879 });
console.dir(
{
testnetGateways,
ardriveRecord,
protocolBalance,
arnsStats: {
'registered domains': Object.keys(allRecords).length,
ardrive: allRecords.ardrive,
},
oldEpoch,
epoch,
observations,
observation,
distributions,
protocolBalance,
names: Object.keys(allRecords),
},
{ depth: 2 },
);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"warp-contracts-plugin-deploy": "^1.0.13"
},
"dependencies": {
"@permaweb/aoconnect": "^0.0.55",
"arbundles": "0.11.0",
"arweave": "1.14.4",
"axios": "1.7.2",
Expand Down
7 changes: 4 additions & 3 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ArconnectSigner, ArweaveSigner } from 'arbundles';
import { GQLNodeInterface, Transaction } from 'warp-contracts';

import { RemoteContract, WarpContract } from './common/index.js';
import { IOContractInteractionsWithIOFees } from './contract-state.js';
import {
ANTRecord,
ANTState,
Expand All @@ -33,6 +32,7 @@ import {
GatewayConnectionSettings,
GatewayMetadata,
GatewayStakingSettings,
IOContractInteractionsWithIOFees,
Observations,
RegistrationType,
WeightedObserver,
Expand Down Expand Up @@ -275,8 +275,9 @@ export interface ArIOWriteContract extends ArIOReadContract {
): Promise<WriteInteractionResult>;
}

// we only support L1 smartweave interactions
export type WriteInteractionResult = Transaction;
export type AoMessageResult = { id: string };
export type SmartWeaveInteractionResult = Transaction;
export type WriteInteractionResult = SmartWeaveInteractionResult;

// Helper type to overwrite properties of A with B
type Overwrite<T, U> = {
Expand Down
5 changes: 3 additions & 2 deletions src/common/ar-io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export class ArIOReadable implements ArIOReadContract {
const state = await this.contract.getState(params);
return state;
}

/**
* @param domain @type {string} The domain name.
* @param evaluationOptions @type {EvaluationOptions} The evaluation options.
Expand Down Expand Up @@ -391,7 +392,7 @@ export class ArIOReadable implements ArIOReadContract {
* ```
*/
async getGateways({ evaluationOptions }: EvaluationParameters = {}): Promise<
Record<string, Gateway> | Record<string, never>
Record<WalletAddress, Gateway> | Record<string, never>
> {
return this.contract.readInteraction({
functionName: AR_IO_CONTRACT_FUNCTIONS.GATEWAYS,
Expand Down Expand Up @@ -430,7 +431,7 @@ export class ArIOReadable implements ArIOReadContract {
* @example
* The current epoch
* ```ts
* arIO.getEpoch({ blockeHeight: 1000 });
* arIO.getEpoch({ blockHeight: 1000 });
* ```
* @example
* Get the epoch at a given block height or sortkey
Expand Down
184 changes: 184 additions & 0 deletions src/common/contracts/ao-process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { connect } from '@permaweb/aoconnect';
import { createData } from 'arbundles';

import { AOContract, ContractSigner, Logger } from '../../types.js';
import { version } from '../../version.js';
import { DefaultLogger } from '../logger.js';

export class AOProcess implements AOContract {
private logger: Logger;
private processId: string;
// private scheduler: string;
private ao: {
result: any;

Check warning on line 29 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 29 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
results: any;

Check warning on line 30 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 30 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
message: any;

Check warning on line 31 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 31 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
spawn: any;

Check warning on line 32 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 32 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
monitor: any;

Check warning on line 33 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 33 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
unmonitor: any;

Check warning on line 34 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 34 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
dryrun: any;

Check warning on line 35 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 35 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
assign: any;

Check warning on line 36 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (18.x, lint)

Unexpected any. Specify a different type

Check warning on line 36 in src/common/contracts/ao-process.ts

View workflow job for this annotation

GitHub Actions / build / build (20.x, lint)

Unexpected any. Specify a different type
};

constructor({
processId,
// connectionConfig,
// scheduler = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA',
logger = new DefaultLogger({ level: 'info' }),
}: {
processId: string;
scheduler?: string;
connectionConfig?: {
CU_URL: string;
MU_URL: string;
GATEWAY_URL: string;
GRAPHQL_URL: string;
};
logger?: DefaultLogger;
}) {
this.processId = processId;
this.logger = logger;
// this.scheduler = scheduler;
this.ao = connect();
}

// TODO: could abstract into our own interface that constructs different signers
async createAoSigner(
signer: ContractSigner,
): Promise<
(args: {
data: string | Buffer;
tags?: { name: string; value: string }[];
target?: string;
anchor?: string;
}) => Promise<{ id: string; raw: ArrayBuffer }>
> {
// ensure appropriate permissions are granted with injected signers.
if (signer.publicKey === undefined && 'setPublicKey' in signer) {
await signer.setPublicKey();
}

const aoSigner = async ({ data, tags, target, anchor }) => {
const dataItem = createData(data, signer, { tags, target, anchor });
const signedData = dataItem.sign(signer).then(async () => ({
id: await dataItem.id,
raw: await dataItem.getRaw(),
}));
return signedData;
};

return aoSigner;
}

async read<K>({
tags,
}: {
tags?: Array<{ name: string; value: string }>;
}): Promise<K> {
this.logger.debug(`Evaluating read interaction on contract`, {
tags,
});
// map tags to inputs
const result = await this.ao.dryrun({
process: this.processId,
tags,
});

if (result.Error !== undefined) {
throw new Error(result.Error);
}

if (result.Messages.length === 0) {
throw new Error('Process does not support provided action.');
}

this.logger.debug(`Read interaction result`, {
result: result.Messages[0].Data,
});

const data: K = JSON.parse(result.Messages[0].Data);
return data;
}

async send<I, K>({
tags,
data,
signer,
}: {
tags: Array<{ name: string; value: string }>;
data?: I;
signer: ContractSigner;
}): Promise<{ id: string; result?: K }> {
this.logger.debug(`Evaluating send interaction on contract`, {
tags,
data,
processId: this.processId,
});

// append ar-io-sdk tags

const messageId = await this.ao.message({
process: this.processId,
// TODO: any other default tags we want to add?
tags: [...tags, { name: 'AR-IO-SDK', value: version }],
data: JSON.stringify(data),
signer: await this.createAoSigner(signer),
});

this.logger.debug(`Sent message to process`, {
messageId,
processId: this.processId,
});

// check the result of the send interaction
const output = await this.ao.result({
message: messageId,
process: this.processId,
});

this.logger.debug('Message result', {
output,
messageId,
processId: this.processId,
});

// check if there are any Messages in the output
if (output.Messages.length === 0) {
return { id: messageId };
}

const tagsOutput = output.Messages[0].Tags;
const error = tagsOutput.find((tag) => tag.name === 'Error');
// if there's an Error tag, throw an error related to it
if (error) {
const result = output.Messages[0].Data;
throw new Error(`${error.Value}: ${result}`);
}

const resultData: K = JSON.parse(output.Messages[0].Data);

this.logger.debug('Message result data', {
resultData,
messageId,
processId: this.processId,
});

return { id: messageId, result: resultData };
}
}
18 changes: 11 additions & 7 deletions src/common/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,23 @@ export class UnknownError extends BaseError {}

export class WriteInteractionError extends BaseError {}

export const INVALID_SIGNER_ERROR =
'Invalid signer. Please provide a valid signer to interact with the contract.';

export class InvalidSignerError extends BaseError {
constructor() {
super(INVALID_SIGNER_ERROR);
super(
'Invalid signer. Please provide a valid signer to interact with the contract.',
);
}
}
export const INVALID_CONTRACT_CONFIGURATION_ERROR =
'Invalid contract configuration';

export class InvalidContractConfigurationError extends BaseError {
constructor() {
super(INVALID_CONTRACT_CONFIGURATION_ERROR);
super('Invalid contract configuration');
}
}

export class InvalidProcessConfigurationError extends BaseError {
constructor() {
super('Invalid process configuration');
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ export * from './ar-io.js';
export * from './error.js';
export * from './logger.js';
export * from './ant.js';
export * from './io.js';

// contracts
export * from './contracts/remote-contract.js';
export * from './contracts/warp-contract.js';

// ao
export * from './contracts/ao-process.js';
Loading

0 comments on commit 19e6f7d

Please sign in to comment.