From 6118cea0b1d5aa027f97e81b34849bd56107a396 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 28 May 2024 21:47:31 -0600 Subject: [PATCH 01/29] feat(ao): experiment with initial implementation of ao contract --- examples/esm/index.mjs | 74 +++++++++++++------------ package.json | 1 + src/common.ts | 18 +++++-- src/common/ar-io.ts | 83 +++++++++++++++++++++++++++-- src/common/contracts/ao-contract.ts | 77 ++++++++++++++++++++++++++ src/common/index.ts | 3 ++ src/contract-state.ts | 20 +++++++ src/utils/smartweave.ts | 26 ++++++--- yarn.lock | 55 +++++++++++++++++++ 9 files changed, 308 insertions(+), 49 deletions(-) create mode 100644 src/common/contracts/ao-contract.ts diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index e9ebf03e..29de7562 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -1,39 +1,43 @@ -import { ARNS_TESTNET_REGISTRY_TX, ArIO } from '@ar.io/sdk'; +import { ArIO } from '@ar.io/sdk'; (async () => { - const arIO = ArIO.init({ - contractTxId: ARNS_TESTNET_REGISTRY_TX, + // const arIO = ArIO.init({ + // contractTxId: ARNS_TESTNET_REGISTRY_TX, + // }); + // // testnet gateways + // const testnetGateways = await arIO.getGateways(); + // const protocolBalance = await arIO.getBalance({ + // address: ARNS_TESTNET_REGISTRY_TX, + // }); + // const ardriveRecord = await arIO.getArNSRecord({ domain: '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(); + // console.dir( + // { + // testnetGateways, + // ardriveRecord, + // protocolBalance, + // arnsStats: { + // 'registered domains': Object.keys(allRecords).length, + // ardrive: allRecords.ardrive, + // }, + // oldEpoch, + // epoch, + // observations, + // observation, + // distributions, + // }, + // { depth: 2 }, + // ); + const process = ArIO.init({ + processId: 'GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc', }); - // testnet gateways - const testnetGateways = await arIO.getGateways(); - const protocolBalance = await arIO.getBalance({ - address: ARNS_TESTNET_REGISTRY_TX, - }); - const ardriveRecord = await arIO.getArNSRecord({ domain: '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(); - - console.dir( - { - testnetGateways, - ardriveRecord, - protocolBalance, - arnsStats: { - 'registered domains': Object.keys(allRecords).length, - ardrive: allRecords.ardrive, - }, - oldEpoch, - epoch, - observations, - observation, - distributions, - }, - { depth: 2 }, - ); + const state = await process.getState(); + console.log({ state }); })(); diff --git a/package.json b/package.json index 7f146786..61ff0c1a 100644 --- a/package.json +++ b/package.json @@ -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.4.0", diff --git a/src/common.ts b/src/common.ts index 508ad63d..8facbe78 100644 --- a/src/common.ts +++ b/src/common.ts @@ -18,7 +18,7 @@ import { ArconnectSigner, ArweaveSigner } from 'arbundles'; import { DataItem } from 'warp-arbundles'; import { Transaction } from 'warp-contracts'; -import { RemoteContract, WarpContract } from './common/index.js'; +import { AOProcess, RemoteContract, WarpContract } from './common/index.js'; import { ANTRecord, ANTState, @@ -59,6 +59,13 @@ export type ContractConfiguration> = | { contractTxId?: string; }; +export type ProcessConfiguration> = + | { + process: AOProcess; + } + | { + processId: string; + }; export type EvaluationOptions = { evalTo?: { sortKey: SortKey } | { blockHeight: BlockHeight }; @@ -71,12 +78,13 @@ export type EvaluationParameters> = { } & T; export type ReadParameters = { - functionName: string; + functionName?: string; inputs?: Input; + tags?: Array<{ name: string; value: string }>; }; export type WriteParameters = WithSigner< - Required> + Required, 'tags'>> >; export interface BaseContract { @@ -91,6 +99,10 @@ export interface ReadContract { }: EvaluationParameters>): Promise; } +export interface AOContract { + read({ tags }): Promise; +} + export interface WriteContract { writeInteraction({ functionName, diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 0162af70..f0127df2 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -17,6 +17,7 @@ import { ARNS_TESTNET_REGISTRY_TX } from '../constants.js'; import { AR_IO_CONTRACT_FUNCTIONS, + AoIOState, ArIOReadContract, ArIOState, ArIOWriteContract, @@ -34,6 +35,7 @@ import { JoinNetworkParams, Observations, OptionalSigner, + ProcessConfiguration, RegistrationType, TransactionId, UpdateGatewaySettingsParams, @@ -46,9 +48,15 @@ import { import { isContractConfiguration, isContractTxIdConfiguration, + isProcessConfiguration, + isProcessIdConfiguration, } from '../utils/smartweave.js'; import { RemoteContract } from './contracts/remote-contract.js'; -import { InvalidContractConfigurationError, WarpContract } from './index.js'; +import { + AOProcess, + InvalidContractConfigurationError, + WarpContract, +} from './index.js'; export class ArIO { /** @@ -147,8 +155,11 @@ export class ArIO { export class ArIOReadable implements ArIOReadContract { protected contract: RemoteContract | WarpContract; + protected process: AOProcess; - constructor(config?: ContractConfiguration) { + constructor( + config?: ContractConfiguration | ProcessConfiguration, + ) { if (!config) { this.contract = new RemoteContract({ contractTxId: ARNS_TESTNET_REGISTRY_TX, @@ -159,6 +170,12 @@ export class ArIOReadable implements ArIOReadContract { this.contract = new RemoteContract({ contractTxId: config.contractTxId, }); + } else if (isProcessConfiguration(config)) { + this.process = config.process; + } else if (isProcessIdConfiguration(config)) { + this.process = new AOProcess({ + processId: config.processId, + }); } else { throw new InvalidContractConfigurationError(); } @@ -178,10 +195,16 @@ export class ArIOReadable implements ArIOReadContract { * arIO.getState({ evaluationOptions: { evalTo: { sortKey: 'mySortKey' } } }); * ``` */ - async getState(params: EvaluationParameters = {}): Promise { + async getState( + params: EvaluationParameters = {}, + ): Promise { + if (this.process instanceof AOProcess) { + return this.process.getState() as T; + } const state = await this.contract.getState(params); - return state; + return state as T; } + /** * @param domain @type {string} The domain name. * @param evaluationOptions @type {EvaluationOptions} The evaluation options. @@ -203,6 +226,12 @@ export class ArIOReadable implements ArIOReadContract { }: EvaluationParameters<{ domain: string }>): Promise< ArNSNameData | undefined > { + // handle ao by sending tags + if (this.process instanceof AOProcess) { + return this.process.read({ + tags: [{ name: 'Action', value: 'Record' }], + }); + } const records = await this.getArNSRecords({ evaluationOptions }); return records[domain]; } @@ -224,6 +253,11 @@ export class ArIOReadable implements ArIOReadContract { async getArNSRecords({ evaluationOptions, }: EvaluationParameters = {}): Promise> { + if (this.process instanceof AOProcess) { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Records' }], + }); + } const state = await this.contract.getState({ evaluationOptions }); return state.records; } @@ -247,6 +281,11 @@ export class ArIOReadable implements ArIOReadContract { }: EvaluationParameters): Promise< Record | Record > { + if (this.process instanceof AOProcess) { + return this.process.read>({ + tags: [{ name: 'Action', value: 'ReservedNames' }], + }); + } const state = await this.contract.getState({ evaluationOptions }); return state.reserved; } @@ -272,6 +311,14 @@ export class ArIOReadable implements ArIOReadContract { }: EvaluationParameters<{ domain: string }>): Promise< ArNSReservedNameData | undefined > { + if (this.process instanceof AOProcess) { + return this.process.read>({ + tags: [ + { name: 'Action', value: 'ReservedName' }, + { name: 'Name', value: domain }, + ], + }); + } const reservedNames = await this.getArNSReservedNames({ evaluationOptions, }); @@ -298,6 +345,14 @@ export class ArIOReadable implements ArIOReadContract { address, evaluationOptions, }: EvaluationParameters<{ address: string }>): Promise { + if (this.process instanceof AOProcess) { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Balance' }, + { name: 'Address', value: address }, + ], + }); + } const balances = await this.getBalances({ evaluationOptions }); return balances[address] || 0; } @@ -320,6 +375,11 @@ export class ArIOReadable implements ArIOReadContract { async getBalances({ evaluationOptions }: EvaluationParameters = {}): Promise< Record > { + if (this.process instanceof AOProcess) { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Balances' }], + }); + } const state = await this.contract.getState({ evaluationOptions }); return state.balances; } @@ -344,6 +404,14 @@ export class ArIOReadable implements ArIOReadContract { address, evaluationOptions, }: EvaluationParameters<{ address: string }>): Promise { + if (this.process instanceof AOProcess) { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Gateway' }, + { name: 'Address', value: address }, + ], + }); + } return this.contract .readInteraction<{ target: string }, Gateway>({ functionName: AR_IO_CONTRACT_FUNCTIONS.GATEWAY, @@ -373,8 +441,13 @@ export class ArIOReadable implements ArIOReadContract { * ``` */ async getGateways({ evaluationOptions }: EvaluationParameters = {}): Promise< - Record | Record + Record | Record > { + if (this.process instanceof AOProcess) { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Gateways' }], + }); + } return this.contract.readInteraction({ functionName: AR_IO_CONTRACT_FUNCTIONS.GATEWAYS, evaluationOptions, diff --git a/src/common/contracts/ao-contract.ts b/src/common/contracts/ao-contract.ts new file mode 100644 index 00000000..b1de3a51 --- /dev/null +++ b/src/common/contracts/ao-contract.ts @@ -0,0 +1,77 @@ +/** + * 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 . + */ +import { dryrun } from '@permaweb/aoconnect'; + +import { AOContract, BaseContract, Logger } from '../../types.js'; +import { DefaultLogger } from '../logger.js'; + +export class AOProcess implements BaseContract, AOContract { + private logger: Logger; + private processId: string; + + constructor({ + processId, + logger = new DefaultLogger(), + }: { + processId: string; + logger?: DefaultLogger; + }) { + this.processId = processId; + this.logger = logger; + } + + async getState(): Promise { + this.logger.info(`Fetching process state`, { + process: this.processId, + }); + const state = await this.read({ + tags: [{ name: 'Action', value: 'State' }], + }); + return state; + } + + async read({ + tags, + }: { + tags?: Array<{ name: string; value: string }>; + }): Promise { + this.logger.debug(`Evaluating read interaction on contract`, { + tags, + }); + // map tags to inputs + const result = await 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.info(`Read interaction result`, { + result: result.Messages[0].Data, + }); + + const data = JSON.parse(result.Messages[0].Data); + + return data as K; + } +} diff --git a/src/common/index.ts b/src/common/index.ts index fa827f22..6418b38c 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -22,3 +22,6 @@ export * from './ant.js'; // contracts export * from './contracts/remote-contract.js'; export * from './contracts/warp-contract.js'; + +// ao +export * from './contracts/ao-contract.js'; diff --git a/src/contract-state.ts b/src/contract-state.ts index 60f5a9b1..6a03026e 100644 --- a/src/contract-state.ts +++ b/src/contract-state.ts @@ -239,6 +239,26 @@ export interface ArIOState { prescribedObservers: PrescribedObservers; } +export interface AoIOState { + GatewayRegistry: Record; + Epochs: Record< + number, + { + // TODO: distributions + observations: EpochObservations; + prescribedObservers: WeightedObserver[]; + startTimestamp: number; + endTimestamp: number; + distributionTimestamp: number; + } + >; + NameRegistry: Record; + Balances: Record; + Vaults: Record; + Ticker: string; + Name: string; +} + // ANT export type ANTRecord = { diff --git a/src/utils/smartweave.ts b/src/utils/smartweave.ts index a38dce97..dff24daa 100644 --- a/src/utils/smartweave.ts +++ b/src/utils/smartweave.ts @@ -17,9 +17,9 @@ import Arweave from 'arweave'; import { EvaluationManifest } from 'warp-contracts'; -import { RemoteContract, WarpContract } from '../common/index.js'; +import { AOProcess, RemoteContract, WarpContract } from '../common/index.js'; import { SORT_KEY_REGEX } from '../constants.js'; -import { ContractConfiguration, SortKey } from '../types.js'; +import { SortKey } from '../types.js'; import { tagsToObject, validateArweaveId } from './arweave.js'; export function isSortKey(sortKey: string): sortKey is SortKey { @@ -75,16 +75,30 @@ export async function getContractManifest({ return contractManifest; } -export function isContractConfiguration( - config: ContractConfiguration, -): config is { +export function isContractConfiguration(config: object): config is { contract: WarpContract | RemoteContract; } { return 'contract' in config; } +export function isProcessConfiguration( + config: object, +): config is { process: AOProcess } { + return 'process' in config; +} + +export function isProcessIdConfiguration( + config: object, +): config is { processId: string } { + return ( + 'processId' in config && + typeof config.processId === 'string' && + validateArweaveId(config.processId) === true + ); +} + export function isContractTxIdConfiguration( - config: ContractConfiguration, + config: object, ): config is { contractTxId: string } { return ( 'contractTxId' in config && diff --git a/yarn.lock b/yarn.lock index 880ce7e9..00cdc500 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1687,6 +1687,29 @@ dependencies: "@octokit/openapi-types" "^22.2.0" +"@permaweb/ao-scheduler-utils@~0.0.16": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@permaweb/ao-scheduler-utils/-/ao-scheduler-utils-0.0.19.tgz#69d35c19583624ace3f500f53b4b4d73fca883e1" + integrity sha512-xwIe9FqQ1UZxEYWvSGJDONz0xr4vDq2Ny1NeRUiO0dKYoonShN+oI1ULgrHocKOjOPNEgRX70vMCKGLe+3x70A== + dependencies: + lru-cache "^10.2.2" + ramda "^0.30.0" + zod "^3.23.5" + +"@permaweb/aoconnect@^0.0.55": + version "0.0.55" + resolved "https://registry.yarnpkg.com/@permaweb/aoconnect/-/aoconnect-0.0.55.tgz#d856a078d3702154ac58541d09478d25ed3acf2c" + integrity sha512-W2GtLZedVseuDkCKk4CmM9SFmi0DdrMKqvhMBm9xo65z+Mzr/t1TEjMJKRNzEA2qh5IdwM43sWJ5fmbBYLg6TQ== + dependencies: + "@permaweb/ao-scheduler-utils" "~0.0.16" + buffer "^6.0.3" + debug "^4.3.4" + hyper-async "^1.1.2" + mnemonist "^0.39.8" + ramda "^0.29.1" + warp-arbundles "^1.0.4" + zod "^3.22.4" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -5361,6 +5384,11 @@ husky@^8.0.3: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +hyper-async@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/hyper-async/-/hyper-async-1.1.2.tgz#b9a83be36e726bface6f4a5b84f1a1a25bf19e6a" + integrity sha512-cnpOgKa+5FZOaccTtjduac1FrZuSc38/ftCp3vYJdUMt+7c+uvGDKLDK4MTNK8D3aFjIeveVrPcSgUPvzZLopg== + iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -7317,6 +7345,13 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mnemonist@^0.39.8: + version "0.39.8" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.39.8.tgz#9078cd8386081afd986cca34b52b5d84ea7a4d38" + integrity sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ== + dependencies: + obliterator "^2.0.1" + modify-values@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -8024,6 +8059,11 @@ object.values@^1.1.7: define-properties "^1.2.1" es-object-atoms "^1.0.0" +obliterator@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" + integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== + once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -8614,6 +8654,16 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +ramda@^0.29.1: + version "0.29.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.29.1.tgz#408a6165b9555b7ba2fc62555804b6c5a2eca196" + integrity sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA== + +ramda@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.30.0.tgz#3cc4f0ddddfa6334dad2f371bd72c33237d92cd0" + integrity sha512-13Y0iMhIQuAm/wNGBL/9HEqIfRGmNmjKnTPlKWfA9f7dnDkr8d45wQ+S7+ZLh/Pq9PdcGxkqKUEA7ySu1QSd9Q== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -10458,3 +10508,8 @@ zip-stream@^4.1.0: archiver-utils "^3.0.4" compress-commons "^4.1.2" readable-stream "^3.6.0" + +zod@^3.22.4, zod@^3.23.5: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== From 82ac19fc62881851f4bf5f53cfbbe916bde18f0e Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Wed, 29 May 2024 10:17:44 -0600 Subject: [PATCH 02/29] chore(api): add other AO compatible apis --- examples/esm/index.mjs | 3 +++ src/common/ar-io.ts | 16 ++++++++++++++++ src/common/contracts/ao-contract.ts | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index 29de7562..0499adcb 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -40,4 +40,7 @@ import { ArIO } from '@ar.io/sdk'; }); const state = await process.getState(); console.log({ state }); + + const prescribedObservers = await process.getPrescribedObservers(); + console.log({ prescribedObservers }); })(); diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index f0127df2..2d85aeda 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -472,6 +472,11 @@ export class ArIOReadable implements ArIOReadContract { async getCurrentEpoch({ evaluationOptions, }: EvaluationParameters = {}): Promise { + if (this.process instanceof AOProcess) { + return this.process.read({ + tags: [{ name: 'Action', value: 'Epoch' }], + }); + } return this.contract.readInteraction({ functionName: AR_IO_CONTRACT_FUNCTIONS.EPOCH, evaluationOptions, @@ -500,6 +505,12 @@ export class ArIOReadable implements ArIOReadContract { }: { blockHeight: number; } & EvaluationParameters): Promise { + if (this.process instanceof AOProcess) { + // TODO: handle tag set as epoch number + return this.process.read({ + tags: [{ name: 'Action', value: 'Epoch' }], + }); + } return this.contract.readInteraction< { height: number }, EpochDistributionData @@ -530,6 +541,11 @@ export class ArIOReadable implements ArIOReadContract { async getPrescribedObservers({ evaluationOptions, }: EvaluationParameters = {}): Promise { + if (this.process instanceof AOProcess) { + return this.process.read({ + tags: [{ name: 'Action', value: 'PrescribedObservers' }], + }); + } return this.contract.readInteraction({ functionName: AR_IO_CONTRACT_FUNCTIONS.PRESCRIBED_OBSERVERS, evaluationOptions, diff --git a/src/common/contracts/ao-contract.ts b/src/common/contracts/ao-contract.ts index b1de3a51..9f71c7ec 100644 --- a/src/common/contracts/ao-contract.ts +++ b/src/common/contracts/ao-contract.ts @@ -66,7 +66,7 @@ export class AOProcess implements BaseContract, AOContract { throw new Error('Process does not support provided action.'); } - this.logger.info(`Read interaction result`, { + this.logger.debug(`Read interaction result`, { result: result.Messages[0].Data, }); From 05b07cfbb1d974e708108c8239d8214d6c50b418 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Thu, 30 May 2024 16:44:13 -0600 Subject: [PATCH 03/29] fix(ao): use types and connect config in ao process to wrap connect from ao --- examples/esm/index.mjs | 7 +--- src/common/contracts/ao-contract.ts | 59 +++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index 0499adcb..813286eb 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -38,9 +38,6 @@ import { ArIO } from '@ar.io/sdk'; const process = ArIO.init({ processId: 'GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc', }); - const state = await process.getState(); - console.log({ state }); - - const prescribedObservers = await process.getPrescribedObservers(); - console.log({ prescribedObservers }); + const records = await process.getArNSRecords(); + console.log(records); })(); diff --git a/src/common/contracts/ao-contract.ts b/src/common/contracts/ao-contract.ts index 9f71c7ec..d5f7933d 100644 --- a/src/common/contracts/ao-contract.ts +++ b/src/common/contracts/ao-contract.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { dryrun } from '@permaweb/aoconnect'; +import { connect } from '@permaweb/aoconnect'; import { AOContract, BaseContract, Logger } from '../../types.js'; import { DefaultLogger } from '../logger.js'; @@ -22,16 +22,38 @@ import { DefaultLogger } from '../logger.js'; export class AOProcess implements BaseContract, AOContract { private logger: Logger; private processId: string; + // private scheduler: string; + private ao: { + result: any; + results: any; + message: any; + spawn: any; + monitor: any; + unmonitor: any; + dryrun: any; + assign: any; + }; constructor({ processId, + // scheduler = 'default-scheduler-tx-id', + connectionConfig, logger = new DefaultLogger(), }: { processId: string; + // scheduler?: string; + connectionConfig?: { + CU_URL: string; + MU_URL: string; + GATEWAY_URL: string; + GRAPHQL_URL: string; + }; logger?: DefaultLogger; }) { this.processId = processId; + // this.scheduler = scheduler; this.logger = logger; + this.ao = connect(connectionConfig); } async getState(): Promise { @@ -53,7 +75,7 @@ export class AOProcess implements BaseContract, AOContract { tags, }); // map tags to inputs - const result = await dryrun({ + const result = await this.ao.dryrun({ process: this.processId, tags, }); @@ -70,8 +92,37 @@ export class AOProcess implements BaseContract, AOContract { result: result.Messages[0].Data, }); - const data = JSON.parse(result.Messages[0].Data); + const data: K = JSON.parse(result.Messages[0].Data); + return data; + } + + async send({ + tags, + data, + }: { + tags: Array<{ name: string; value: string }>; + data: K; + }): Promise { + this.logger.debug(`Evaluating send interaction on contract`, { + tags, + data, + }); + + const result = await this.ao.message({ + process: this.processId, + tags, + data: JSON.stringify(data), + }); + + if (result.Error !== undefined) { + throw new Error(result.Error); + } + + this.logger.debug(`Send interaction result`, { + result, + }); - return data as K; + const res: K = JSON.parse(result.Messages[0].Data); + return res; } } From aab8967c83e69fafe1258b768b1e33cb3920aeb8 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 31 May 2024 09:11:27 -0600 Subject: [PATCH 04/29] feat(IO): implement io/ao classes that call process apis --- src/common.ts | 11 +- src/common/ar-io.ts | 455 +++++++++++++++++++++++----- src/common/contracts/ao-contract.ts | 20 +- src/common/error.ts | 18 +- src/constants.ts | 2 + 5 files changed, 409 insertions(+), 97 deletions(-) diff --git a/src/common.ts b/src/common.ts index 8facbe78..0cd57ea5 100644 --- a/src/common.ts +++ b/src/common.ts @@ -23,7 +23,6 @@ import { ANTRecord, ANTState, AllowedProtocols, - ArIOState, ArNSAuctionData, ArNSNameData, ArNSReservedNameData, @@ -59,6 +58,7 @@ export type ContractConfiguration> = | { contractTxId?: string; }; + export type ProcessConfiguration> = | { process: AOProcess; @@ -101,6 +101,7 @@ export interface ReadContract { export interface AOContract { read({ tags }): Promise; + send({ tags, data }): Promise<{ id: string }>; } export interface WriteContract { @@ -114,7 +115,7 @@ export interface WriteContract { } // TODO: extend with additional methods -export interface ArIOReadContract extends BaseContract { +export interface ArIOReadContract extends BaseContract { getGateway({ address, evaluationOptions, @@ -255,7 +256,11 @@ export interface ArIOWriteContract { }): Promise; } -export type WriteInteractionResult = Transaction | DataItem; +export type AOMessageResult = { id: string }; +export type SmartWeaveInteractionResult = Transaction | DataItem; +export type WriteInteractionResult = + | SmartWeaveInteractionResult + | AOMessageResult; // Helper type to overwrite properties of A with B type Overwrite = { diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 2d85aeda..12715925 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { ARNS_TESTNET_REGISTRY_TX } from '../constants.js'; +import { ARNS_TESTNET_REGISTRY_TX, ioDevnetProcessId } from '../constants.js'; import { AR_IO_CONTRACT_FUNCTIONS, AoIOState, @@ -142,7 +142,9 @@ export class ArIO { // must be a WarpContract to get a ArIOWriteable { contract: WarpContract } | { contractTxId: string } >): ArIOWritable; - static init(config?: OptionalSigner>) { + static init( + config?: OptionalSigner>, + ): ArIOReadable | ArIOWritable { if (config && config.signer) { const { signer, ...rest } = config; const contract = this.createWriteableContract(rest); @@ -153,9 +155,8 @@ export class ArIO { } } -export class ArIOReadable implements ArIOReadContract { +export class ArIOReadable implements ArIOReadContract { protected contract: RemoteContract | WarpContract; - protected process: AOProcess; constructor( config?: ContractConfiguration | ProcessConfiguration, @@ -170,12 +171,6 @@ export class ArIOReadable implements ArIOReadContract { this.contract = new RemoteContract({ contractTxId: config.contractTxId, }); - } else if (isProcessConfiguration(config)) { - this.process = config.process; - } else if (isProcessIdConfiguration(config)) { - this.process = new AOProcess({ - processId: config.processId, - }); } else { throw new InvalidContractConfigurationError(); } @@ -198,9 +193,6 @@ export class ArIOReadable implements ArIOReadContract { async getState( params: EvaluationParameters = {}, ): Promise { - if (this.process instanceof AOProcess) { - return this.process.getState() as T; - } const state = await this.contract.getState(params); return state as T; } @@ -226,12 +218,6 @@ export class ArIOReadable implements ArIOReadContract { }: EvaluationParameters<{ domain: string }>): Promise< ArNSNameData | undefined > { - // handle ao by sending tags - if (this.process instanceof AOProcess) { - return this.process.read({ - tags: [{ name: 'Action', value: 'Record' }], - }); - } const records = await this.getArNSRecords({ evaluationOptions }); return records[domain]; } @@ -253,11 +239,6 @@ export class ArIOReadable implements ArIOReadContract { async getArNSRecords({ evaluationOptions, }: EvaluationParameters = {}): Promise> { - if (this.process instanceof AOProcess) { - return this.process.read>({ - tags: [{ name: 'Action', value: 'Records' }], - }); - } const state = await this.contract.getState({ evaluationOptions }); return state.records; } @@ -281,11 +262,6 @@ export class ArIOReadable implements ArIOReadContract { }: EvaluationParameters): Promise< Record | Record > { - if (this.process instanceof AOProcess) { - return this.process.read>({ - tags: [{ name: 'Action', value: 'ReservedNames' }], - }); - } const state = await this.contract.getState({ evaluationOptions }); return state.reserved; } @@ -311,14 +287,6 @@ export class ArIOReadable implements ArIOReadContract { }: EvaluationParameters<{ domain: string }>): Promise< ArNSReservedNameData | undefined > { - if (this.process instanceof AOProcess) { - return this.process.read>({ - tags: [ - { name: 'Action', value: 'ReservedName' }, - { name: 'Name', value: domain }, - ], - }); - } const reservedNames = await this.getArNSReservedNames({ evaluationOptions, }); @@ -345,14 +313,6 @@ export class ArIOReadable implements ArIOReadContract { address, evaluationOptions, }: EvaluationParameters<{ address: string }>): Promise { - if (this.process instanceof AOProcess) { - return this.process.read({ - tags: [ - { name: 'Action', value: 'Balance' }, - { name: 'Address', value: address }, - ], - }); - } const balances = await this.getBalances({ evaluationOptions }); return balances[address] || 0; } @@ -375,11 +335,6 @@ export class ArIOReadable implements ArIOReadContract { async getBalances({ evaluationOptions }: EvaluationParameters = {}): Promise< Record > { - if (this.process instanceof AOProcess) { - return this.process.read>({ - tags: [{ name: 'Action', value: 'Balances' }], - }); - } const state = await this.contract.getState({ evaluationOptions }); return state.balances; } @@ -404,14 +359,6 @@ export class ArIOReadable implements ArIOReadContract { address, evaluationOptions, }: EvaluationParameters<{ address: string }>): Promise { - if (this.process instanceof AOProcess) { - return this.process.read({ - tags: [ - { name: 'Action', value: 'Gateway' }, - { name: 'Address', value: address }, - ], - }); - } return this.contract .readInteraction<{ target: string }, Gateway>({ functionName: AR_IO_CONTRACT_FUNCTIONS.GATEWAY, @@ -443,11 +390,6 @@ export class ArIOReadable implements ArIOReadContract { async getGateways({ evaluationOptions }: EvaluationParameters = {}): Promise< Record | Record > { - if (this.process instanceof AOProcess) { - return this.process.read>({ - tags: [{ name: 'Action', value: 'Gateways' }], - }); - } return this.contract.readInteraction({ functionName: AR_IO_CONTRACT_FUNCTIONS.GATEWAYS, evaluationOptions, @@ -472,11 +414,6 @@ export class ArIOReadable implements ArIOReadContract { async getCurrentEpoch({ evaluationOptions, }: EvaluationParameters = {}): Promise { - if (this.process instanceof AOProcess) { - return this.process.read({ - tags: [{ name: 'Action', value: 'Epoch' }], - }); - } return this.contract.readInteraction({ functionName: AR_IO_CONTRACT_FUNCTIONS.EPOCH, evaluationOptions, @@ -505,12 +442,6 @@ export class ArIOReadable implements ArIOReadContract { }: { blockHeight: number; } & EvaluationParameters): Promise { - if (this.process instanceof AOProcess) { - // TODO: handle tag set as epoch number - return this.process.read({ - tags: [{ name: 'Action', value: 'Epoch' }], - }); - } return this.contract.readInteraction< { height: number }, EpochDistributionData @@ -541,11 +472,6 @@ export class ArIOReadable implements ArIOReadContract { async getPrescribedObservers({ evaluationOptions, }: EvaluationParameters = {}): Promise { - if (this.process instanceof AOProcess) { - return this.process.read({ - tags: [{ name: 'Action', value: 'PrescribedObservers' }], - }); - } return this.contract.readInteraction({ functionName: AR_IO_CONTRACT_FUNCTIONS.PRESCRIBED_OBSERVERS, evaluationOptions, @@ -1007,3 +933,374 @@ export class ArIOWritable extends ArIOReadable implements ArIOWriteContract { }); } } + +export class IOReadable + implements + Omit< + ArIOReadContract, + 'getAuction' | 'getAuctions' | 'getEpoch' + > +{ + protected process: AOProcess; + + constructor(config?: ProcessConfiguration) { + if (!config) { + this.process = new AOProcess({ + processId: ioDevnetProcessId, + }); + } else if (isProcessConfiguration(config)) { + this.process = config.process; + } else if (isProcessIdConfiguration(config)) { + this.process = new AOProcess({ + processId: config.processId, + }); + } else { + throw new InvalidContractConfigurationError(); + } + } + + async getState(): Promise { + return this.process.getState(); + } + + async getArNSRecord({ + domain, + }: { + domain: string; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Record' }, + { name: 'Name', value: domain }, + ], + }); + } + + async getArNSRecords(): Promise> { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Records' }], + }); + } + + async getArNSReservedNames(): Promise< + Record | Record + > { + return this.process.read>({ + tags: [{ name: 'Action', value: 'ReservedNames' }], + }); + } + + async getArNSReservedName({ + domain, + }: { + domain: string; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'ReservedName' }, + { name: 'Name', value: domain }, + ], + }); + } + + async getBalance({ address }: { address: WalletAddress }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Balance' }, + { name: 'Address', value: address }, + ], + }); + } + + async getBalances(): Promise> { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Balances' }], + }); + } + + async getGateway({ + address, + }: { + address: WalletAddress; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Gateway' }, + { name: 'Address', value: address }, + ], + }); + } + + async getGateways(): Promise< + Record | Record + > { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Gateways' }], + }); + } + + async getCurrentEpoch(): Promise { + return this.process.read({ + tags: [{ name: 'Action', value: 'Epoch' }], + }); + } + + async getPrescribedObservers(): Promise { + return this.process.read({ + tags: [{ name: 'Action', value: 'PrescribedObservers' }], + }); + } + + async getObservations(): Promise { + return this.process.read({ + tags: [{ name: 'Action', value: 'Observations' }], + }); + } + + async getDistributions(): Promise { + return this.process.read({ + tags: [{ name: 'Action', value: 'Distributions' }], + }); + } +} + +export class IO { + static init({ processId }: { processId: string }): IOReadable; + static init({ process }: { process: AOProcess }): IOReadable; + static init( + config: WithSigner< + { process: AOProcess } | { processId: string } + >, + ): IOWriteable; + static init( + config?: OptionalSigner>, + ): IOReadable { + if (config && config.signer) { + const { signer, ...rest } = config; + return new IOWriteable({ + ...rest, + signer, + }); + } + return new IOReadable(config); + } +} + +export class IOWriteable extends IOReadable implements ArIOWriteContract { + protected declare process: AOProcess; + private signer: ContractSigner; + constructor({ + signer, + ...config + }: WithSigner< + | { + process?: AOProcess; + } + | { processId?: string } + >) { + if (Object.keys(config).length === 0) { + super({ + process: new AOProcess({ + processId: ioDevnetProcessId, + }), + }); + this.signer = signer; + } else if (isProcessConfiguration(config)) { + super({ process: config.process }); + this.signer = signer; + } else if (isProcessIdConfiguration(config)) { + super({ + process: new AOProcess({ + processId: config.processId, + }), + }); + this.signer = signer; + } else { + throw new InvalidContractConfigurationError(); + } + } + + async transfer({ + target, + qty, + }: { + target: string; + qty: number | mIOToken; + }): Promise { + return this.process.send<{ + target: WalletAddress; + qty: number; + denomination?: DENOMINATIONS; + }>({ + tags: [{ name: 'Action', value: 'Transfer' }], + data: { + target, + qty: qty.valueOf(), + }, + signer: this.signer, + }); + } + + async joinNetwork({ + qty, + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }: JoinNetworkParams): Promise { + return this.process.send({ + signer: this.signer, + tags: [{ name: 'Action', value: 'JoinNetwork' }], + data: { + qty: qty.valueOf(), + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake: minDelegatedStake.valueOf(), + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }, + }); + } + + async updateGatewaySettings({ + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }: UpdateGatewaySettingsParams): Promise { + return this.process.send({ + signer: this.signer, + tags: [{ name: 'Action', value: 'UpdateGatewaySettings' }], + data: { + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake: minDelegatedStake?.valueOf(), + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }, + }); + } + + async increaseDelegateStake(params: { + target: string; + qty: number | mIOToken; + }): Promise { + return this.process.send<{ target: string; qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'IncreaseDelegateStake' }], + data: { + target: params.target, + qty: params.qty.valueOf(), + }, + }); + } + + async decreaseDelegateStake(params: { + target: string; + qty: number | mIOToken; + }): Promise { + return this.process.send<{ target: string; qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'DecreaseDelegateStake' }], + data: { + target: params.target, + qty: params.qty.valueOf(), + }, + }); + } + + async increaseOperatorStake(params: { + qty: number | mIOToken; + }): Promise { + return this.process.send<{ qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'IncreaseOperatorStake' }], + data: { + qty: params.qty.valueOf(), + }, + }); + } + + async decreaseOperatorStake(params: { + qty: number | mIOToken; + }): Promise { + return this.process.send<{ qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'DecreaseOperatorStake' }], + data: { + qty: params.qty.valueOf(), + }, + }); + } + + async saveObservations(params: { + reportTxId: TransactionId; + failedGateways: WalletAddress[]; + }): Promise { + return this.process.send<{ + observerReportTxId: TransactionId; + failedGateways: WalletAddress[]; + }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'SaveObservations' }], + data: { + observerReportTxId: params.reportTxId, + failedGateways: params.failedGateways, + }, + }); + } + + async extendLease(params: { + domain: string; + years: number; + }): Promise { + return this.process.send<{ name: string; years: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'ExtendLease' }], + data: { + name: params.domain, + years: params.years, + }, + }); + } + + async increaseUndernameLimit(params: { + domain: string; + qty: number; + }): Promise { + return this.process.send<{ name: string; qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'IncreaseUndernameLimit' }], + data: { + name: params.domain, + qty: params.qty, + }, + }); + } +} diff --git a/src/common/contracts/ao-contract.ts b/src/common/contracts/ao-contract.ts index d5f7933d..6cd6fbb7 100644 --- a/src/common/contracts/ao-contract.ts +++ b/src/common/contracts/ao-contract.ts @@ -16,13 +16,17 @@ */ import { connect } from '@permaweb/aoconnect'; -import { AOContract, BaseContract, Logger } from '../../types.js'; +import { + AOContract, + BaseContract, + ContractSigner, + Logger, +} from '../../types.js'; import { DefaultLogger } from '../logger.js'; export class AOProcess implements BaseContract, AOContract { private logger: Logger; private processId: string; - // private scheduler: string; private ao: { result: any; results: any; @@ -36,12 +40,10 @@ export class AOProcess implements BaseContract, AOContract { constructor({ processId, - // scheduler = 'default-scheduler-tx-id', connectionConfig, logger = new DefaultLogger(), }: { processId: string; - // scheduler?: string; connectionConfig?: { CU_URL: string; MU_URL: string; @@ -51,7 +53,6 @@ export class AOProcess implements BaseContract, AOContract { logger?: DefaultLogger; }) { this.processId = processId; - // this.scheduler = scheduler; this.logger = logger; this.ao = connect(connectionConfig); } @@ -99,10 +100,12 @@ export class AOProcess implements BaseContract, AOContract { async send({ tags, data, + signer, }: { tags: Array<{ name: string; value: string }>; data: K; - }): Promise { + signer: ContractSigner; + }): Promise<{ id: string }> { this.logger.debug(`Evaluating send interaction on contract`, { tags, data, @@ -112,6 +115,7 @@ export class AOProcess implements BaseContract, AOContract { process: this.processId, tags, data: JSON.stringify(data), + signer, }); if (result.Error !== undefined) { @@ -122,7 +126,7 @@ export class AOProcess implements BaseContract, AOContract { result, }); - const res: K = JSON.parse(result.Messages[0].Data); - return res; + const id: string = JSON.parse(result.Messages[0].Id); + return { id }; } } diff --git a/src/common/error.ts b/src/common/error.ts index e9061f10..834ff7bc 100644 --- a/src/common/error.ts +++ b/src/common/error.ts @@ -35,18 +35,22 @@ 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'); } } diff --git a/src/constants.ts b/src/constants.ts index a67bf937..1103597a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -31,4 +31,6 @@ export const ARNS_TESTNET_REGISTRY_TX = export const ARNS_DEVNET_REGISTRY_TX = '_NctcA2sRy1-J4OmIQZbYFPM17piNcbdBPH2ncX2RL8'; +export const ioDevnetProcessId = 'GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc'; + export const MIO_PER_IO = 1_000_000; From 2d4a068178146aca084f11e82329ac0cdc62f1ac Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 31 May 2024 12:24:51 -0600 Subject: [PATCH 05/29] chore(types): add AO specific types for gateways and epochs --- src/common.ts | 8 ++-- src/common/ar-io.ts | 51 ++++++++-------------- src/common/contracts/ao-contract.ts | 10 +++-- src/contract-state.ts | 65 ++++++++++++++++++++--------- 4 files changed, 74 insertions(+), 60 deletions(-) diff --git a/src/common.ts b/src/common.ts index 0cd57ea5..76e21d2c 100644 --- a/src/common.ts +++ b/src/common.ts @@ -23,6 +23,8 @@ import { ANTRecord, ANTState, AllowedProtocols, + AoEpochData, + AoGateway, ArNSAuctionData, ArNSNameData, ArNSReservedNameData, @@ -120,12 +122,12 @@ export interface ArIOReadContract extends BaseContract { address, evaluationOptions, }: EvaluationParameters<{ address: WalletAddress }>): Promise< - Gateway | undefined + Gateway | AoGateway | undefined >; getGateways({ evaluationOptions, }: EvaluationParameters): Promise< - Record | Record + Record | Record >; getBalance( params: { address: WalletAddress } & EvaluationOptions, @@ -165,7 +167,7 @@ export interface ArIOReadContract extends BaseContract { }>): Promise; getCurrentEpoch({ evaluationOptions, - }: EvaluationParameters): Promise; + }: EvaluationParameters): Promise; getPrescribedObservers({ evaluationOptions, }: EvaluationParameters): Promise; diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 12715925..202b7476 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -17,6 +17,8 @@ import { ARNS_TESTNET_REGISTRY_TX, ioDevnetProcessId } from '../constants.js'; import { AR_IO_CONTRACT_FUNCTIONS, + AoEpochData, + AoGateway, AoIOState, ArIOReadContract, ArIOState, @@ -427,7 +429,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 @@ -1022,8 +1024,8 @@ export class IOReadable address, }: { address: WalletAddress; - }): Promise { - return this.process.read({ + }): Promise { + return this.process.read({ tags: [ { name: 'Action', value: 'Gateway' }, { name: 'Address', value: address }, @@ -1032,16 +1034,19 @@ export class IOReadable } async getGateways(): Promise< - Record | Record + Record | Record > { - return this.process.read>({ + return this.process.read>({ tags: [{ name: 'Action', value: 'Gateways' }], }); } - async getCurrentEpoch(): Promise { - return this.process.read({ - tags: [{ name: 'Action', value: 'Epoch' }], + async getCurrentEpoch(): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Epoch' }, + { name: 'Timestamp', value: `${Date.now()}` }, + ], }); } @@ -1064,43 +1069,21 @@ export class IOReadable } } -export class IO { - static init({ processId }: { processId: string }): IOReadable; - static init({ process }: { process: AOProcess }): IOReadable; - static init( - config: WithSigner< - { process: AOProcess } | { processId: string } - >, - ): IOWriteable; - static init( - config?: OptionalSigner>, - ): IOReadable { - if (config && config.signer) { - const { signer, ...rest } = config; - return new IOWriteable({ - ...rest, - signer, - }); - } - return new IOReadable(config); - } -} - export class IOWriteable extends IOReadable implements ArIOWriteContract { - protected declare process: AOProcess; + protected declare process: AOProcess; private signer: ContractSigner; constructor({ signer, ...config }: WithSigner< | { - process?: AOProcess; + process?: AOProcess; } | { processId?: string } >) { if (Object.keys(config).length === 0) { super({ - process: new AOProcess({ + process: new AOProcess({ processId: ioDevnetProcessId, }), }); @@ -1110,7 +1093,7 @@ export class IOWriteable extends IOReadable implements ArIOWriteContract { this.signer = signer; } else if (isProcessIdConfiguration(config)) { super({ - process: new AOProcess({ + process: new AOProcess({ processId: config.processId, }), }); diff --git a/src/common/contracts/ao-contract.ts b/src/common/contracts/ao-contract.ts index 6cd6fbb7..fc357ee8 100644 --- a/src/common/contracts/ao-contract.ts +++ b/src/common/contracts/ao-contract.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { connect } from '@permaweb/aoconnect'; +import { connect, dryrun } from '@permaweb/aoconnect'; import { AOContract, @@ -27,6 +27,7 @@ import { DefaultLogger } from '../logger.js'; export class AOProcess implements BaseContract, AOContract { private logger: Logger; private processId: string; + // private scheduler: string; private ao: { result: any; results: any; @@ -40,10 +41,12 @@ export class AOProcess implements BaseContract, AOContract { constructor({ processId, - connectionConfig, + // connectionConfig, + // scheduler = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA', logger = new DefaultLogger(), }: { processId: string; + scheduler?: string; connectionConfig?: { CU_URL: string; MU_URL: string; @@ -54,7 +57,8 @@ export class AOProcess implements BaseContract, AOContract { }) { this.processId = processId; this.logger = logger; - this.ao = connect(connectionConfig); + // this.scheduler = scheduler; + this.ao = connect(); } async getState(): Promise { diff --git a/src/contract-state.ts b/src/contract-state.ts index 6a03026e..76497535 100644 --- a/src/contract-state.ts +++ b/src/contract-state.ts @@ -239,26 +239,6 @@ export interface ArIOState { prescribedObservers: PrescribedObservers; } -export interface AoIOState { - GatewayRegistry: Record; - Epochs: Record< - number, - { - // TODO: distributions - observations: EpochObservations; - prescribedObservers: WeightedObserver[]; - startTimestamp: number; - endTimestamp: number; - distributionTimestamp: number; - } - >; - NameRegistry: Record; - Balances: Record; - Vaults: Record; - Ticker: string; - Name: string; -} - // ANT export type ANTRecord = { @@ -288,3 +268,48 @@ export const ANT_CONTRACT_FUNCTIONS = { BALANCE: 'balance', EVOLVE: 'evolve', }; + +// AO Contract types +export type AoEpochData = { + epochIndex: number; + observations: EpochObservations; + prescribedObservers: WeightedObserver[]; + startTimestamp: number; + endTimestamp: number; + distributionTimestamp: number; + distributions: Record; +}; + +export type AoGatewayStats = { + passedConsecutiveEpochs: number; + failedConsecutiveEpochs: number; + totalEpochParticipationCount: number; + passedEpochCount: number; + failedEpochCount: number; + observedEpochCount: number; + prescribedEpochCount: number; +}; + +export type AoGateway = { + settings: GatewaySettings; + stats: AoGatewayStats; + delegates: Record; + totalDelegatedStake: number; + vaults: Record; + startTimestamp: number; + endTimestamp: number; + observerAddress: WalletAddress; + operatorStake: number; + status: 'joined' | 'leaving'; + // TODO: add weights +}; + +export interface AoIOState { + GatewayRegistry: Record; + Epochs: Record; + NameRegistry: Record; + Balances: Record; + Vaults: Record; + Ticker: string; + Name: string; +} From 797671813b571ca16dbb5e6c8f57bce5fa3a13de Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 31 May 2024 13:02:30 -0600 Subject: [PATCH 06/29] chore(types): fix types --- src/common/ar-io.ts | 31 +++++++++++++++++++++++++---- src/common/contracts/ao-contract.ts | 2 +- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 202b7476..0d417465 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -936,6 +936,29 @@ export class ArIOWritable extends ArIOReadable implements ArIOWriteContract { } } +// AO/IO Contract +export class IO { + static init({ processId }: { processId: string }): IOReadable; + static init({ process }: { process: AOProcess }): IOReadable; + static init( + config: WithSigner< + { process: AOProcess } | { processId: string } + >, + ): IOWriteable; + static init( + config?: OptionalSigner>, + ): IOReadable { + if (config && config.signer) { + const { signer, ...rest } = config; + return new IOWriteable({ + ...rest, + signer, + }); + } + return new IOReadable(config); + } +} + export class IOReadable implements Omit< @@ -1070,20 +1093,20 @@ export class IOReadable } export class IOWriteable extends IOReadable implements ArIOWriteContract { - protected declare process: AOProcess; + protected declare process: AOProcess; private signer: ContractSigner; constructor({ signer, ...config }: WithSigner< | { - process?: AOProcess; + process?: AOProcess; } | { processId?: string } >) { if (Object.keys(config).length === 0) { super({ - process: new AOProcess({ + process: new AOProcess({ processId: ioDevnetProcessId, }), }); @@ -1093,7 +1116,7 @@ export class IOWriteable extends IOReadable implements ArIOWriteContract { this.signer = signer; } else if (isProcessIdConfiguration(config)) { super({ - process: new AOProcess({ + process: new AOProcess({ processId: config.processId, }), }); diff --git a/src/common/contracts/ao-contract.ts b/src/common/contracts/ao-contract.ts index fc357ee8..c66a1ca1 100644 --- a/src/common/contracts/ao-contract.ts +++ b/src/common/contracts/ao-contract.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { connect, dryrun } from '@permaweb/aoconnect'; +import { connect } from '@permaweb/aoconnect'; import { AOContract, From 3cf0d6396bcf41720c17c5e8d6384e18cc9730af Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Mon, 3 Jun 2024 09:26:40 -0600 Subject: [PATCH 07/29] chore(example): update esm example to use IO --- examples/esm/index.mjs | 59 +++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index 813286eb..bb868f89 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -1,43 +1,26 @@ -import { ArIO } from '@ar.io/sdk'; +import { IO, ioDevnetProcessId } from '@ar.io/sdk'; (async () => { - // const arIO = ArIO.init({ - // contractTxId: ARNS_TESTNET_REGISTRY_TX, - // }); - // // testnet gateways - // const testnetGateways = await arIO.getGateways(); - // const protocolBalance = await arIO.getBalance({ - // address: ARNS_TESTNET_REGISTRY_TX, - // }); - // const ardriveRecord = await arIO.getArNSRecord({ domain: '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(); - // console.dir( - // { - // testnetGateways, - // ardriveRecord, - // protocolBalance, - // arnsStats: { - // 'registered domains': Object.keys(allRecords).length, - // ardrive: allRecords.ardrive, - // }, - // oldEpoch, - // epoch, - // observations, - // observation, - // distributions, - // }, - // { depth: 2 }, - // ); - const process = ArIO.init({ + const arIO = IO.init({ processId: 'GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc', }); - const records = await process.getArNSRecords(); - console.log(records); + // // testnet gateways + const testnetGateways = await arIO.getGateways(); + const protocolBalance = await arIO.getBalance({ + address: ioDevnetProcessId, + }); + const ardriveRecord = await arIO.getArNSRecord({ domain: 'ardrive' }); + const allRecords = await arIO.getArNSRecords(); + console.dir( + { + testnetGateways, + ardriveRecord, + protocolBalance, + arnsStats: { + 'registered domains': Object.keys(allRecords).length, + ardrive: allRecords.ardrive, + }, + }, + { depth: 2 }, + ); })(); From d96fa5928a1cc45639fe3e0f687726eba059a762 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Mon, 3 Jun 2024 20:12:30 -0600 Subject: [PATCH 08/29] fix(io): separate out io/ao contract interfaces They are similar, but some differences. Separting them out will make it clear how they can be used --- src/common.ts | 18 +- src/common/ar-io.ts | 400 +--------------- .../{ao-contract.ts => ao-process.ts} | 0 src/common/index.ts | 2 +- src/common/io.ts | 432 ++++++++++++++++++ src/contract-state.ts | 55 +-- src/io.d.ts | 202 ++++++++ 7 files changed, 649 insertions(+), 460 deletions(-) rename src/common/contracts/{ao-contract.ts => ao-process.ts} (100%) create mode 100644 src/common/io.ts create mode 100644 src/io.d.ts diff --git a/src/common.ts b/src/common.ts index 2bb64d11..a43d0ebc 100644 --- a/src/common.ts +++ b/src/common.ts @@ -22,8 +22,6 @@ import { ANTRecord, ANTState, AllowedProtocols, - AoEpochData, - AoGateway, ArNSAuctionData, ArNSNameData, ArNSReservedNameData, @@ -68,10 +66,10 @@ export type ContractConfiguration> = export type ProcessConfiguration> = | { - process: AOProcess; + process?: AOProcess; } | { - processId: string; + processId?: string; }; export type EvaluationOptions = { @@ -128,12 +126,12 @@ export interface ArIOReadContract extends BaseContract { address, evaluationOptions, }: EvaluationParameters<{ address: WalletAddress }>): Promise< - Gateway | AoGateway | undefined + Gateway | undefined >; getGateways({ evaluationOptions, }: EvaluationParameters): Promise< - Record | Record + Record | Record >; getBalance( params: { address: WalletAddress } & EvaluationOptions, @@ -167,7 +165,7 @@ export interface ArIOReadContract extends BaseContract { }>): Promise; getCurrentEpoch({ evaluationOptions, - }: EvaluationParameters): Promise; + }: EvaluationParameters): Promise; getPrescribedObservers({ evaluationOptions, }: EvaluationParameters): Promise; @@ -296,11 +294,9 @@ export interface ArIOWriteContract extends ArIOReadContract { ): Promise; } -export type AOMessageResult = { id: string }; +export type AoMessageResult = { id: string }; export type SmartWeaveInteractionResult = Transaction; -export type WriteInteractionResult = - | SmartWeaveInteractionResult - | AOMessageResult; +export type WriteInteractionResult = SmartWeaveInteractionResult; // Helper type to overwrite properties of A with B type Overwrite = { diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 8a6c6f7c..5ec4ed32 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -14,12 +14,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { ARNS_TESTNET_REGISTRY_TX, ioDevnetProcessId } from '../constants.js'; +import { ARNS_TESTNET_REGISTRY_TX } from '../constants.js'; import { AR_IO_CONTRACT_FUNCTIONS, - AoEpochData, - AoGateway, - AoIOState, ArIOReadContract, ArIOState, ArIOWriteContract, @@ -37,7 +34,6 @@ import { JoinNetworkParams, Observations, OptionalSigner, - ProcessConfiguration, RegistrationType, TransactionId, UpdateGatewaySettingsParams, @@ -51,15 +47,9 @@ import { import { isContractConfiguration, isContractTxIdConfiguration, - isProcessConfiguration, - isProcessIdConfiguration, } from '../utils/smartweave.js'; import { RemoteContract } from './contracts/remote-contract.js'; -import { - AOProcess, - InvalidContractConfigurationError, - WarpContract, -} from './index.js'; +import { InvalidContractConfigurationError, WarpContract } from './index.js'; export class ArIO { /** @@ -206,9 +196,7 @@ export class ArIOReadable implements ArIOReadContract { * arIO.getState({ evaluationOptions: { evalTo: { sortKey: 'mySortKey' } } }); * ``` */ - async getState( - params: EvaluationParameters = {}, - ): Promise { + async getState(params: EvaluationParameters = {}): Promise { const state = await this.contract.getState(params); return state as T; } @@ -1062,385 +1050,3 @@ export class ArIOWritable ); } } - -// AO/IO Contract -export class IO { - static init({ processId }: { processId: string }): IOReadable; - static init({ process }: { process: AOProcess }): IOReadable; - static init( - config: WithSigner< - { process: AOProcess } | { processId: string } - >, - ): IOWriteable; - static init( - config?: OptionalSigner>, - ): IOReadable { - if (config && config.signer) { - const { signer, ...rest } = config; - return new IOWriteable({ - ...rest, - signer, - }); - } - return new IOReadable(config); - } -} - -export class IOReadable - implements - Omit< - ArIOReadContract, - 'getAuction' | 'getAuctions' | 'getEpoch' | 'getPriceForInteraction' - > -{ - protected process: AOProcess; - - constructor(config?: ProcessConfiguration) { - if (!config) { - this.process = new AOProcess({ - processId: ioDevnetProcessId, - }); - } else if (isProcessConfiguration(config)) { - this.process = config.process; - } else if (isProcessIdConfiguration(config)) { - this.process = new AOProcess({ - processId: config.processId, - }); - } else { - throw new InvalidContractConfigurationError(); - } - } - - async getState(): Promise { - return this.process.getState(); - } - - async getArNSRecord({ - domain, - }: { - domain: string; - }): Promise { - return this.process.read({ - tags: [ - { name: 'Action', value: 'Record' }, - { name: 'Name', value: domain }, - ], - }); - } - - async getArNSRecords(): Promise> { - return this.process.read>({ - tags: [{ name: 'Action', value: 'Records' }], - }); - } - - async getArNSReservedNames(): Promise< - Record | Record - > { - return this.process.read>({ - tags: [{ name: 'Action', value: 'ReservedNames' }], - }); - } - - async getArNSReservedName({ - domain, - }: { - domain: string; - }): Promise { - return this.process.read({ - tags: [ - { name: 'Action', value: 'ReservedName' }, - { name: 'Name', value: domain }, - ], - }); - } - - async getBalance({ address }: { address: WalletAddress }): Promise { - return this.process.read({ - tags: [ - { name: 'Action', value: 'Balance' }, - { name: 'Address', value: address }, - ], - }); - } - - async getBalances(): Promise> { - return this.process.read>({ - tags: [{ name: 'Action', value: 'Balances' }], - }); - } - - async getGateway({ - address, - }: { - address: WalletAddress; - }): Promise { - return this.process.read({ - tags: [ - { name: 'Action', value: 'Gateway' }, - { name: 'Address', value: address }, - ], - }); - } - - async getGateways(): Promise< - Record | Record - > { - return this.process.read>({ - tags: [{ name: 'Action', value: 'Gateways' }], - }); - } - - async getCurrentEpoch(): Promise { - return this.process.read({ - tags: [ - { name: 'Action', value: 'Epoch' }, - { name: 'Timestamp', value: `${Date.now()}` }, - ], - }); - } - - async getPrescribedObservers(): Promise { - return this.process.read({ - tags: [{ name: 'Action', value: 'PrescribedObservers' }], - }); - } - - async getObservations(): Promise { - return this.process.read({ - tags: [{ name: 'Action', value: 'Observations' }], - }); - } - - async getDistributions(): Promise { - return this.process.read({ - tags: [{ name: 'Action', value: 'Distributions' }], - }); - } -} - -export class IOWriteable - extends IOReadable - implements - Omit< - ArIOReadContract, - 'getAuction' | 'getAuctions' | 'getEpoch' | 'getPriceForInteraction' - > -{ - protected declare process: AOProcess; - private signer: ContractSigner; - constructor({ - signer, - ...config - }: WithSigner< - | { - process?: AOProcess; - } - | { processId?: string } - >) { - if (Object.keys(config).length === 0) { - super({ - process: new AOProcess({ - processId: ioDevnetProcessId, - }), - }); - this.signer = signer; - } else if (isProcessConfiguration(config)) { - super({ process: config.process }); - this.signer = signer; - } else if (isProcessIdConfiguration(config)) { - super({ - process: new AOProcess({ - processId: config.processId, - }), - }); - this.signer = signer; - } else { - throw new InvalidContractConfigurationError(); - } - } - - async transfer({ - target, - qty, - }: { - target: string; - qty: number | mIOToken; - }): Promise { - return this.process.send<{ - target: WalletAddress; - qty: number; - denomination?: DENOMINATIONS; - }>({ - tags: [{ name: 'Action', value: 'Transfer' }], - data: { - target, - qty: qty.valueOf(), - }, - signer: this.signer, - }); - } - - async joinNetwork({ - qty, - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake, - note, - port, - properties, - protocol, - autoStake, - observerWallet, - }: JoinNetworkParams): Promise { - return this.process.send({ - signer: this.signer, - tags: [{ name: 'Action', value: 'JoinNetwork' }], - data: { - qty: qty.valueOf(), - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake: minDelegatedStake.valueOf(), - note, - port, - properties, - protocol, - autoStake, - observerWallet, - }, - }); - } - - async updateGatewaySettings({ - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake, - note, - port, - properties, - protocol, - autoStake, - observerWallet, - }: UpdateGatewaySettingsParams): Promise { - return this.process.send({ - signer: this.signer, - tags: [{ name: 'Action', value: 'UpdateGatewaySettings' }], - data: { - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake: minDelegatedStake?.valueOf(), - note, - port, - properties, - protocol, - autoStake, - observerWallet, - }, - }); - } - - async increaseDelegateStake(params: { - target: string; - qty: number | mIOToken; - }): Promise { - return this.process.send<{ target: string; qty: number }>({ - signer: this.signer, - tags: [{ name: 'Action', value: 'IncreaseDelegateStake' }], - data: { - target: params.target, - qty: params.qty.valueOf(), - }, - }); - } - - async decreaseDelegateStake(params: { - target: string; - qty: number | mIOToken; - }): Promise { - return this.process.send<{ target: string; qty: number }>({ - signer: this.signer, - tags: [{ name: 'Action', value: 'DecreaseDelegateStake' }], - data: { - target: params.target, - qty: params.qty.valueOf(), - }, - }); - } - - async increaseOperatorStake(params: { - qty: number | mIOToken; - }): Promise { - return this.process.send<{ qty: number }>({ - signer: this.signer, - tags: [{ name: 'Action', value: 'IncreaseOperatorStake' }], - data: { - qty: params.qty.valueOf(), - }, - }); - } - - async decreaseOperatorStake(params: { - qty: number | mIOToken; - }): Promise { - return this.process.send<{ qty: number }>({ - signer: this.signer, - tags: [{ name: 'Action', value: 'DecreaseOperatorStake' }], - data: { - qty: params.qty.valueOf(), - }, - }); - } - - async saveObservations(params: { - reportTxId: TransactionId; - failedGateways: WalletAddress[]; - }): Promise { - return this.process.send<{ - observerReportTxId: TransactionId; - failedGateways: WalletAddress[]; - }>({ - signer: this.signer, - tags: [{ name: 'Action', value: 'SaveObservations' }], - data: { - observerReportTxId: params.reportTxId, - failedGateways: params.failedGateways, - }, - }); - } - - async extendLease(params: { - domain: string; - years: number; - }): Promise { - return this.process.send<{ name: string; years: number }>({ - signer: this.signer, - tags: [{ name: 'Action', value: 'ExtendLease' }], - data: { - name: params.domain, - years: params.years, - }, - }); - } - - async increaseUndernameLimit(params: { - domain: string; - qty: number; - }): Promise { - return this.process.send<{ name: string; qty: number }>({ - signer: this.signer, - tags: [{ name: 'Action', value: 'IncreaseUndernameLimit' }], - data: { - name: params.domain, - qty: params.qty, - }, - }); - } -} diff --git a/src/common/contracts/ao-contract.ts b/src/common/contracts/ao-process.ts similarity index 100% rename from src/common/contracts/ao-contract.ts rename to src/common/contracts/ao-process.ts diff --git a/src/common/index.ts b/src/common/index.ts index 6418b38c..7165e8c3 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -24,4 +24,4 @@ export * from './contracts/remote-contract.js'; export * from './contracts/warp-contract.js'; // ao -export * from './contracts/ao-contract.js'; +export * from './contracts/ao-process.js'; diff --git a/src/common/io.ts b/src/common/io.ts new file mode 100644 index 00000000..b1480967 --- /dev/null +++ b/src/common/io.ts @@ -0,0 +1,432 @@ +/** + * 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 . + */ +import { ioDevnetProcessId } from '../constants.js'; +import { + ArNSNameData, + ArNSReservedNameData, + DENOMINATIONS, + EpochDistributionData, + Observations, + WeightedObserver, +} from '../contract-state.js'; +import { + AoEpochData, + AoGateway, + AoIORead, + AoIOState, + AoIOWrite, +} from '../io.js'; +import { mIOToken } from '../token.js'; +import { + AoMessageResult, + ContractSigner, + JoinNetworkParams, + OptionalSigner, + ProcessConfiguration, + TransactionId, + UpdateGatewaySettingsParams, + WalletAddress, + WithSigner, +} from '../types.js'; +import { + isProcessConfiguration, + isProcessIdConfiguration, +} from '../utils/smartweave.js'; +import { AOProcess } from './contracts/ao-process.js'; +import { InvalidContractConfigurationError } from './error.js'; + +export class IO { + static init(): AoIORead; + static init({ + process, + signer, + }: WithSigner<{ process: AOProcess }>): AoIOWrite; + static init({ + processId, + signer, + }: WithSigner<{ + processId: string; + }>): AoIOWrite; + static init({ processId }: { processId: string }): AoIORead; + static init( + config?: OptionalSigner>, + ): AoIORead | AoIOWrite { + if (config && config.signer) { + const { signer, ...rest } = config; + return new IOWriteable({ + ...rest, + signer, + }); + } + return new IOReadable(config); + } +} + +export class IOReadable implements AoIORead { + protected process: AOProcess; + + constructor(config?: ProcessConfiguration) { + if (!config) { + this.process = new AOProcess({ + processId: ioDevnetProcessId, + }); + } else if (isProcessConfiguration(config)) { + this.process = config.process; + } else if (isProcessIdConfiguration(config)) { + this.process = new AOProcess({ + processId: config.processId, + }); + } else { + throw new InvalidContractConfigurationError(); + } + } + + async getEpoch({ + blockHeight, + }: { + blockHeight: number; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Epoch' }, + { name: 'BlockHeight', value: blockHeight.toString() }, + ], + }); + } + + async getArNSRecord({ + domain, + }: { + domain: string; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Record' }, + { name: 'Name', value: domain }, + ], + }); + } + + async getArNSRecords(): Promise> { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Records' }], + }); + } + + async getArNSReservedNames(): Promise< + Record | Record + > { + return this.process.read>({ + tags: [{ name: 'Action', value: 'ReservedNames' }], + }); + } + + async getArNSReservedName({ + domain, + }: { + domain: string; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'ReservedName' }, + { name: 'Name', value: domain }, + ], + }); + } + + async getBalance({ address }: { address: WalletAddress }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Balance' }, + { name: 'Address', value: address }, + ], + }); + } + + async getBalances(): Promise> { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Balances' }], + }); + } + + async getGateway({ + address, + }: { + address: WalletAddress; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Gateway' }, + { name: 'Address', value: address }, + ], + }); + } + + async getGateways(): Promise< + Record | Record + > { + return this.process.read>({ + tags: [{ name: 'Action', value: 'Gateways' }], + }); + } + + async getCurrentEpoch(): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Epoch' }, + { name: 'Timestamp', value: `${Date.now()}` }, + ], + }); + } + + async getPrescribedObservers(): Promise { + return this.process.read({ + tags: [{ name: 'Action', value: 'PrescribedObservers' }], + }); + } + + async getObservations(): Promise { + return this.process.read({ + tags: [{ name: 'Action', value: 'Observations' }], + }); + } + + async getDistributions(): Promise { + return this.process.read({ + tags: [{ name: 'Action', value: 'Distributions' }], + }); + } +} + +export class IOWriteable extends IOReadable implements AoIOWrite { + protected declare process: AOProcess; + private signer: ContractSigner; + constructor({ + signer, + ...config + }: WithSigner< + | { + process?: AOProcess; + } + | { processId?: string } + >) { + if (Object.keys(config).length === 0) { + super({ + process: new AOProcess({ + processId: ioDevnetProcessId, + }), + }); + this.signer = signer; + } else if (isProcessConfiguration(config)) { + super({ process: config.process }); + this.signer = signer; + } else if (isProcessIdConfiguration(config)) { + super({ + process: new AOProcess({ + processId: config.processId, + }), + }); + this.signer = signer; + } else { + throw new InvalidContractConfigurationError(); + } + } + + async transfer({ + target, + qty, + }: { + target: string; + qty: number | mIOToken; + }): Promise { + return this.process.send<{ + target: WalletAddress; + qty: number; + denomination?: DENOMINATIONS; + }>({ + tags: [{ name: 'Action', value: 'Transfer' }], + data: { + target, + qty: qty.valueOf(), + }, + signer: this.signer, + }); + } + + async joinNetwork({ + qty, + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }: JoinNetworkParams): Promise { + return this.process.send({ + signer: this.signer, + tags: [{ name: 'Action', value: 'JoinNetwork' }], + data: { + qty: qty.valueOf(), + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake: minDelegatedStake.valueOf(), + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }, + }); + } + + async updateGatewaySettings({ + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }: UpdateGatewaySettingsParams): Promise { + return this.process.send({ + signer: this.signer, + tags: [{ name: 'Action', value: 'UpdateGatewaySettings' }], + data: { + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake: minDelegatedStake?.valueOf(), + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }, + }); + } + + async increaseDelegateStake(params: { + target: string; + qty: number | mIOToken; + }): Promise { + return this.process.send<{ target: string; qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'IncreaseDelegateStake' }], + data: { + target: params.target, + qty: params.qty.valueOf(), + }, + }); + } + + async decreaseDelegateStake(params: { + target: string; + qty: number | mIOToken; + }): Promise { + return this.process.send<{ target: string; qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'DecreaseDelegateStake' }], + data: { + target: params.target, + qty: params.qty.valueOf(), + }, + }); + } + + async increaseOperatorStake(params: { + qty: number | mIOToken; + }): Promise { + return this.process.send<{ qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'IncreaseOperatorStake' }], + data: { + qty: params.qty.valueOf(), + }, + }); + } + + async decreaseOperatorStake(params: { + qty: number | mIOToken; + }): Promise { + return this.process.send<{ qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'DecreaseOperatorStake' }], + data: { + qty: params.qty.valueOf(), + }, + }); + } + + async saveObservations(params: { + reportTxId: TransactionId; + failedGateways: WalletAddress[]; + }): Promise { + return this.process.send<{ + observerReportTxId: TransactionId; + failedGateways: WalletAddress[]; + }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'SaveObservations' }], + data: { + observerReportTxId: params.reportTxId, + failedGateways: params.failedGateways, + }, + }); + } + + async extendLease(params: { + domain: string; + years: number; + }): Promise { + return this.process.send<{ name: string; years: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'ExtendLease' }], + data: { + name: params.domain, + years: params.years, + }, + }); + } + + async increaseUndernameLimit(params: { + domain: string; + qty: number; + }): Promise { + return this.process.send<{ name: string; qty: number }>({ + signer: this.signer, + tags: [{ name: 'Action', value: 'IncreaseUndernameLimit' }], + data: { + name: params.domain, + qty: params.qty, + }, + }); + } +} diff --git a/src/contract-state.ts b/src/contract-state.ts index 0ffbb4af..f179909a 100644 --- a/src/contract-state.ts +++ b/src/contract-state.ts @@ -16,6 +16,10 @@ */ import { WalletAddress } from './common.js'; +export type Balances = Record; +export type Fees = Record; +export type Observations = Record; + export const ioContractReadInteractions = [ 'gateway', 'gateways', @@ -194,10 +198,6 @@ export enum DENOMINATIONS { MIO = 'mIO', } -export type Balances = Record; - -export type Fees = Record; - export type ReservedNameData = { target?: string; // The target wallet address this name is reserved for endTimestamp?: number; // At what unix time (seconds since epoch) this reserved name becomes available @@ -240,8 +240,6 @@ export type EpochObservations = { reports: Record; // a reference point for the report submitted by this observer }; -export type Observations = Record; - export type EpochDistributionData = { epochZeroStartHeight: number; epochStartHeight: number; // the current epoch start height @@ -302,48 +300,3 @@ export const ANT_CONTRACT_FUNCTIONS = { BALANCE: 'balance', EVOLVE: 'evolve', }; - -// AO Contract types -export type AoEpochData = { - epochIndex: number; - observations: EpochObservations; - prescribedObservers: WeightedObserver[]; - startTimestamp: number; - endTimestamp: number; - distributionTimestamp: number; - distributions: Record; -}; - -export type AoGatewayStats = { - passedConsecutiveEpochs: number; - failedConsecutiveEpochs: number; - totalEpochParticipationCount: number; - passedEpochCount: number; - failedEpochCount: number; - observedEpochCount: number; - prescribedEpochCount: number; -}; - -export type AoGateway = { - settings: GatewaySettings; - stats: AoGatewayStats; - delegates: Record; - totalDelegatedStake: number; - vaults: Record; - startTimestamp: number; - endTimestamp: number; - observerAddress: WalletAddress; - operatorStake: number; - status: 'joined' | 'leaving'; - // TODO: add weights -}; - -export interface AoIOState { - GatewayRegistry: Record; - Epochs: Record; - NameRegistry: Record; - Balances: Record; - Vaults: Record; - Ticker: string; - Name: string; -} diff --git a/src/io.d.ts b/src/io.d.ts new file mode 100644 index 00000000..fd860a94 --- /dev/null +++ b/src/io.d.ts @@ -0,0 +1,202 @@ +/** + * 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 . + */ +export interface AoIORead { + getGateway({ + address, + }: { + address: WalletAddress; + }): Promise; + getGateways(): Promise< + Record | Record + >; + getBalance(params: { address: WalletAddress }): Promise; + getBalances(): Promise | Record>; + getArNSRecord({ + domain, + }: { + domain: string; + }): Promise; + getArNSRecords(): Promise< + Record | Record + >; + getArNSReservedNames(): Promise< + Record | Record + >; + getArNSReservedName({ + domain, + }: { + domain: string; + }): Promise; + getEpoch({ + blockHeight, + }: { + blockHeight: number; + }): Promise; + getCurrentEpoch(): Promise; + getPrescribedObservers(): Promise; + // TODO: allow timestamp or empty + getObservations({ blockHeight }): Promise; + // TODO: allow timestamp or empty + getDistributions({ + blockHeight, + }: { + blockHeight: number; + }): Promise; +} + +export interface AoIOWrite extends AoIORead { + // write interactions + transfer( + { + target, + qty, + }: { + target: WalletAddress; + qty: number; + }, + options?: WriteOptions, + ): Promise; + joinNetwork( + { + qty, + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerAddress, + }: Omit & { + observerAddress?: WalletAddress; + }, + options?: WriteOptions, + ): Promise; + updateGatewaySettings( + { + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerWallet, + }: Omit & { + observerAddress?: WalletAddress; + }, + options?: WriteOptions, + ): Promise; + increaseOperatorStake( + params: { + qty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise; + decreaseOperatorStake( + params: { + qty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise; + increaseDelegateStake( + params: { + target: WalletAddress; + qty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise; + decreaseDelegateStake( + params: { + target: WalletAddress; + qty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise; + saveObservations( + params: { + reportTxId: TransactionId; + failedGateways: WalletAddress[]; + }, + options?: WriteOptions, + ): Promise; + extendLease( + params: { + domain: string; + years: number; + }, + options?: WriteOptions, + ): Promise; + increaseUndernameLimit( + params: { + domain: string; + qty: number; + }, + options?: WriteOptions, + ): Promise; +} + +// AO Contract types +export interface AoIOState { + GatewayRegistry: Record; + Epochs: Record; + NameRegistry: Record; + Balances: Record; + Vaults: Record; + Ticker: string; + Name: string; +} + +export type AoEpochData = { + epochIndex: number; + observations: EpochObservations; + prescribedObservers: WeightedObserver[]; + startTimestamp: number; + endTimestamp: number; + distributionTimestamp: number; + distributions: Record; +}; + +export type AoGatewayStats = { + passedConsecutiveEpochs: number; + failedConsecutiveEpochs: number; + totalEpochParticipationCount: number; + passedEpochCount: number; + failedEpochCount: number; + observedEpochCount: number; + prescribedEpochCount: number; +}; + +export type AoGateway = { + settings: GatewaySettings; + stats: AoGatewayStats; + delegates: Record; + totalDelegatedStake: number; + vaults: Record; + startTimestamp: number; + endTimestamp: number; + observerAddress: WalletAddress; + operatorStake: number; + status: 'joined' | 'leaving'; + // TODO: add weights +}; From 8be31e6d636f61e9dc90f37f02e64fc6d9f86b63 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 14:38:09 -0600 Subject: [PATCH 09/29] chore: cleanup ArIORead interface --- src/common.ts | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/common.ts b/src/common.ts index a43d0ebc..10b6cf49 100644 --- a/src/common.ts +++ b/src/common.ts @@ -163,23 +163,19 @@ export interface ArIOReadContract extends BaseContract { }: EvaluationParameters<{ blockHeight: number; }>): Promise; - getCurrentEpoch({ - evaluationOptions, - }: EvaluationParameters): Promise; - getPrescribedObservers({ - evaluationOptions, - }: EvaluationParameters): Promise; - getObservations({ - evaluationOptions, - }: EvaluationParameters<{ - epochStartHeight?: number; - }>): Promise; - getDistributions({ - evaluationOptions, - }: EvaluationParameters): Promise; - getAuctions({ - evaluationOptions, - }: EvaluationParameters): Promise>; + getCurrentEpoch( + params?: EvaluationParameters, + ): Promise; + getPrescribedObservers( + params?: EvaluationParameters, + ): Promise; + getObservations(params?: EvaluationParameters): Promise; + getDistributions( + params?: EvaluationParameters, + ): Promise; + getAuctions( + params?: EvaluationParameters, + ): Promise>; getAuction({ domain, type, From e30a58636c6e55bf3f9559203cb99cf44d9507eb Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 4 Jun 2024 18:14:47 -0400 Subject: [PATCH 10/29] chore: working on write interactions --- src/common/contracts/ao-process.ts | 4 +++- src/common/index.ts | 1 + src/common/io.ts | 6 +++++- src/{io.d.ts => io.ts} | 23 ++++++++++++++++++++++- src/types.ts | 1 + 5 files changed, 32 insertions(+), 3 deletions(-) rename src/{io.d.ts => io.ts} (92%) diff --git a/src/common/contracts/ao-process.ts b/src/common/contracts/ao-process.ts index c66a1ca1..bcbc2710 100644 --- a/src/common/contracts/ao-process.ts +++ b/src/common/contracts/ao-process.ts @@ -43,7 +43,7 @@ export class AOProcess implements BaseContract, AOContract { processId, // connectionConfig, // scheduler = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA', - logger = new DefaultLogger(), + logger = new DefaultLogger({ level: 'debug' }), }: { processId: string; scheduler?: string; @@ -122,6 +122,8 @@ export class AOProcess implements BaseContract, AOContract { signer, }); + console.log(result); + if (result.Error !== undefined) { throw new Error(result.Error); } diff --git a/src/common/index.ts b/src/common/index.ts index 7165e8c3..4d7e37ba 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -18,6 +18,7 @@ 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'; diff --git a/src/common/io.ts b/src/common/io.ts index b1480967..f1c530dc 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -339,7 +339,11 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }): Promise { return this.process.send<{ target: string; qty: number }>({ signer: this.signer, - tags: [{ name: 'Action', value: 'IncreaseDelegateStake' }], + tags: [ + { name: 'Action', value: 'DelegateStake' }, + { name: 'Target', value: params.target }, + { name: 'Quantity', value: params.qty.valueOf().toString() }, + ], data: { target: params.target, qty: params.qty.valueOf(), diff --git a/src/io.d.ts b/src/io.ts similarity index 92% rename from src/io.d.ts rename to src/io.ts index fd860a94..7a81b031 100644 --- a/src/io.d.ts +++ b/src/io.ts @@ -14,6 +14,27 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { + ArNSNameData, + ArNSReservedNameData, + EpochDistributionData, + EpochObservations, + GatewayDelegate, + GatewaySettings, + Observations, + VaultData, + WeightedObserver, +} from './contract-state.js'; +import { mIOToken } from './token.js'; +import { + AoMessageResult, + JoinNetworkParams, + TransactionId, + UpdateGatewaySettingsParams, + WalletAddress, + WriteOptions, +} from './types.js'; + export interface AoIORead { getGateway({ address, @@ -101,7 +122,7 @@ export interface AoIOWrite extends AoIORead { properties, protocol, autoStake, - observerWallet, + observerAddress, }: Omit & { observerAddress?: WalletAddress; }, diff --git a/src/types.ts b/src/types.ts index e115c6c3..531e84da 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,3 +18,4 @@ export * from './arns-service.js'; export * from './contract-state.js'; export * from './common.js'; export * from './token.js'; +export * from './io.js'; From 4e2f65d79770fee48f5288307cfd7b50048e6d58 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 17:19:44 -0600 Subject: [PATCH 11/29] fix(ao): update send on process to use proper signer and evalute result --- examples/esm/index.mjs | 46 ++++++++----- src/common.ts | 14 +++- src/common/contracts/ao-process.ts | 107 +++++++++++++++++++++-------- src/common/io.ts | 82 +++++++++++----------- src/utils/smartweave.ts | 4 +- 5 files changed, 158 insertions(+), 95 deletions(-) diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index bb868f89..368ceb74 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -1,26 +1,36 @@ -import { IO, ioDevnetProcessId } from '@ar.io/sdk'; +import { ArweaveSigner, IO, ioDevnetProcessId } from '@ar.io/sdk'; +import fs from 'fs'; (async () => { + const wallet = JSON.parse(fs.readFileSync('./wallet.json', 'utf8')); + const signer = new ArweaveSigner(wallet); const arIO = IO.init({ processId: 'GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc', + signer, }); // // testnet gateways - const testnetGateways = await arIO.getGateways(); - const protocolBalance = await arIO.getBalance({ - address: ioDevnetProcessId, + // const testnetGateways = await arIO.getGateways(); + // const protocolBalance = await arIO.getBalance({ + // address: ioDevnetProcessId, + // }); + // const ardriveRecord = await arIO.getArNSRecord({ domain: 'ardrive' }); + // const allRecords = await arIO.getArNSRecords(); + const transfer = await arIO.transfer({ + target: 'ZjmB2vEUlHlJ7-rgJkYP09N5IzLPhJyStVrK5u9dDEo', + qty: 1000000000000, }); - const ardriveRecord = await arIO.getArNSRecord({ domain: 'ardrive' }); - const allRecords = await arIO.getArNSRecords(); - console.dir( - { - testnetGateways, - ardriveRecord, - protocolBalance, - arnsStats: { - 'registered domains': Object.keys(allRecords).length, - ardrive: allRecords.ardrive, - }, - }, - { depth: 2 }, - ); + + console.log(transfer); + // console.dir( + // { + // testnetGateways, + // ardriveRecord, + // protocolBalance, + // arnsStats: { + // 'registered domains': Object.keys(allRecords).length, + // ardrive: allRecords.ardrive, + // }, + // }, + // { depth: 2 }, + // ); })(); diff --git a/src/common.ts b/src/common.ts index 10b6cf49..bfacd6fc 100644 --- a/src/common.ts +++ b/src/common.ts @@ -64,9 +64,9 @@ export type ContractConfiguration> = contractTxId?: string; }; -export type ProcessConfiguration> = +export type ProcessConfiguration = | { - process?: AOProcess; + process?: AOProcess; } | { processId?: string; @@ -110,7 +110,15 @@ export interface ReadContract { export interface AOContract { read({ tags }): Promise; - send({ tags, data }): Promise<{ id: string }>; + send({ + tags, + data, + signer, + }: { + tags: { name: string; value: string }[]; + data: I; + signer: ContractSigner; + }): Promise<{ id: string; result?: K }>; } export interface WriteContract { diff --git a/src/common/contracts/ao-process.ts b/src/common/contracts/ao-process.ts index bcbc2710..2409eb24 100644 --- a/src/common/contracts/ao-process.ts +++ b/src/common/contracts/ao-process.ts @@ -14,17 +14,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { connect } from '@permaweb/aoconnect'; - -import { - AOContract, - BaseContract, - ContractSigner, - Logger, -} from '../../types.js'; +import { connect, message, result } from '@permaweb/aoconnect'; +import { createData } from 'arbundles'; + +import { AOContract, ContractSigner, Logger } from '../../types.js'; import { DefaultLogger } from '../logger.js'; -export class AOProcess implements BaseContract, AOContract { +export class AOProcess implements AOContract { private logger: Logger; private processId: string; // private scheduler: string; @@ -61,14 +57,32 @@ export class AOProcess implements BaseContract, AOContract { this.ao = connect(); } - async getState(): Promise { - this.logger.info(`Fetching process state`, { - process: this.processId, - }); - const state = await this.read({ - tags: [{ name: 'Action', value: 'State' }], - }); - return state; + // 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({ @@ -101,38 +115,71 @@ export class AOProcess implements BaseContract, AOContract { return data; } - async send({ + async send({ tags, data, signer, }: { tags: Array<{ name: string; value: string }>; - data: K; + data?: I; signer: ContractSigner; - }): Promise<{ id: string }> { + }): Promise<{ id: string; result?: K }> { this.logger.debug(`Evaluating send interaction on contract`, { tags, data, + processId: this.processId, }); - const result = await this.ao.message({ + const messageId = await message({ process: this.processId, tags, data: JSON.stringify(data), - signer, + signer: await this.createAoSigner(signer), }); - console.log(result); + this.logger.debug(`Sent message to process`, { + messageId, + proceessId: this.processId, + }); - if (result.Error !== undefined) { - throw new Error(result.Error); - } + // check the result of the send interaction + const output = await result({ + message: messageId, + process: this.processId, + }); - this.logger.debug(`Send interaction result`, { - result, + this.logger.debug('Message result', { + output, + messageId, + processId: this.processId, }); - const id: string = JSON.parse(result.Messages[0].Id); - return { id }; + // 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 + if (error) { + // parse the data + const result = output.Messages[0].Data; + throw new Error(`${error.Value}: ${result}`); + } + + const resultData: K = JSON.parse(output.Messages[0].Data); + + // console.log(result); + + // if (result.Error !== undefined) { + // throw new Error(result.Error); + // } + + // this.logger.debug(`Send interaction result`, { + // result, + // }); + + return { id: messageId, result: resultData }; } } diff --git a/src/common/io.ts b/src/common/io.ts index f1c530dc..5767c325 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -18,18 +18,11 @@ import { ioDevnetProcessId } from '../constants.js'; import { ArNSNameData, ArNSReservedNameData, - DENOMINATIONS, EpochDistributionData, Observations, WeightedObserver, } from '../contract-state.js'; -import { - AoEpochData, - AoGateway, - AoIORead, - AoIOState, - AoIOWrite, -} from '../io.js'; +import { AoEpochData, AoGateway, AoIORead, AoIOWrite } from '../io.js'; import { mIOToken } from '../token.js'; import { AoMessageResult, @@ -54,7 +47,7 @@ export class IO { static init({ process, signer, - }: WithSigner<{ process: AOProcess }>): AoIOWrite; + }: WithSigner<{ process: AOProcess }>): AoIOWrite; static init({ processId, signer, @@ -63,7 +56,7 @@ export class IO { }>): AoIOWrite; static init({ processId }: { processId: string }): AoIORead; static init( - config?: OptionalSigner>, + config?: OptionalSigner, ): AoIORead | AoIOWrite { if (config && config.signer) { const { signer, ...rest } = config; @@ -77,17 +70,17 @@ export class IO { } export class IOReadable implements AoIORead { - protected process: AOProcess; + protected process: AOProcess; - constructor(config?: ProcessConfiguration) { + constructor(config?: ProcessConfiguration) { if (!config) { - this.process = new AOProcess({ + this.process = new AOProcess({ processId: ioDevnetProcessId, }); - } else if (isProcessConfiguration(config)) { + } else if (isProcessConfiguration(config)) { this.process = config.process; } else if (isProcessIdConfiguration(config)) { - this.process = new AOProcess({ + this.process = new AOProcess({ processId: config.processId, }); } else { @@ -213,30 +206,30 @@ export class IOReadable implements AoIORead { } export class IOWriteable extends IOReadable implements AoIOWrite { - protected declare process: AOProcess; + protected declare process: AOProcess; private signer: ContractSigner; constructor({ signer, ...config }: WithSigner< | { - process?: AOProcess; + process?: AOProcess; } | { processId?: string } >) { if (Object.keys(config).length === 0) { super({ - process: new AOProcess({ + process: new AOProcess({ processId: ioDevnetProcessId, }), }); this.signer = signer; - } else if (isProcessConfiguration(config)) { + } else if (isProcessConfiguration(config)) { super({ process: config.process }); this.signer = signer; } else if (isProcessIdConfiguration(config)) { super({ - process: new AOProcess({ + process: new AOProcess({ processId: config.processId, }), }); @@ -253,16 +246,18 @@ export class IOWriteable extends IOReadable implements AoIOWrite { target: string; qty: number | mIOToken; }): Promise { - return this.process.send<{ - target: WalletAddress; - qty: number; - denomination?: DENOMINATIONS; - }>({ - tags: [{ name: 'Action', value: 'Transfer' }], - data: { - target, - qty: qty.valueOf(), - }, + return this.process.send({ + tags: [ + { name: 'Action', value: 'Transfer' }, + { + name: 'Recipient', + value: target, + }, + { + name: 'Quantity', + value: qty.valueOf().toString(), + }, + ], signer: this.signer, }); } @@ -281,7 +276,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { autoStake, observerWallet, }: JoinNetworkParams): Promise { - return this.process.send({ + return this.process.send({ signer: this.signer, tags: [{ name: 'Action', value: 'JoinNetwork' }], data: { @@ -314,7 +309,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { autoStake, observerWallet, }: UpdateGatewaySettingsParams): Promise { - return this.process.send({ + return this.process.send({ signer: this.signer, tags: [{ name: 'Action', value: 'UpdateGatewaySettings' }], data: { @@ -337,7 +332,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { target: string; qty: number | mIOToken; }): Promise { - return this.process.send<{ target: string; qty: number }>({ + return this.process.send({ signer: this.signer, tags: [ { name: 'Action', value: 'DelegateStake' }, @@ -355,7 +350,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { target: string; qty: number | mIOToken; }): Promise { - return this.process.send<{ target: string; qty: number }>({ + return this.process.send({ signer: this.signer, tags: [{ name: 'Action', value: 'DecreaseDelegateStake' }], data: { @@ -368,7 +363,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { async increaseOperatorStake(params: { qty: number | mIOToken; }): Promise { - return this.process.send<{ qty: number }>({ + return this.process.send({ signer: this.signer, tags: [{ name: 'Action', value: 'IncreaseOperatorStake' }], data: { @@ -380,7 +375,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { async decreaseOperatorStake(params: { qty: number | mIOToken; }): Promise { - return this.process.send<{ qty: number }>({ + return this.process.send({ signer: this.signer, tags: [{ name: 'Action', value: 'DecreaseOperatorStake' }], data: { @@ -393,10 +388,13 @@ export class IOWriteable extends IOReadable implements AoIOWrite { reportTxId: TransactionId; failedGateways: WalletAddress[]; }): Promise { - return this.process.send<{ - observerReportTxId: TransactionId; - failedGateways: WalletAddress[]; - }>({ + return this.process.send< + { + observerReportTxId: TransactionId; + failedGateways: WalletAddress[]; + }, + never + >({ signer: this.signer, tags: [{ name: 'Action', value: 'SaveObservations' }], data: { @@ -410,7 +408,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { domain: string; years: number; }): Promise { - return this.process.send<{ name: string; years: number }>({ + return this.process.send({ signer: this.signer, tags: [{ name: 'Action', value: 'ExtendLease' }], data: { @@ -424,7 +422,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { domain: string; qty: number; }): Promise { - return this.process.send<{ name: string; qty: number }>({ + return this.process.send({ signer: this.signer, tags: [{ name: 'Action', value: 'IncreaseUndernameLimit' }], data: { diff --git a/src/utils/smartweave.ts b/src/utils/smartweave.ts index dff24daa..e0fafc86 100644 --- a/src/utils/smartweave.ts +++ b/src/utils/smartweave.ts @@ -81,9 +81,9 @@ export function isContractConfiguration(config: object): config is { return 'contract' in config; } -export function isProcessConfiguration( +export function isProcessConfiguration( config: object, -): config is { process: AOProcess } { +): config is { process: AOProcess } { return 'process' in config; } From 9a3427e70c635ccf4b8c890c23084739faa125e2 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 17:21:06 -0600 Subject: [PATCH 12/29] chore(logs): add debug logs to process interaction --- src/common/contracts/ao-process.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/common/contracts/ao-process.ts b/src/common/contracts/ao-process.ts index 2409eb24..621586a6 100644 --- a/src/common/contracts/ao-process.ts +++ b/src/common/contracts/ao-process.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { connect, message, result } from '@permaweb/aoconnect'; +import { connect } from '@permaweb/aoconnect'; import { createData } from 'arbundles'; import { AOContract, ContractSigner, Logger } from '../../types.js'; @@ -130,7 +130,7 @@ export class AOProcess implements AOContract { processId: this.processId, }); - const messageId = await message({ + const messageId = await this.ao.message({ process: this.processId, tags, data: JSON.stringify(data), @@ -143,7 +143,7 @@ export class AOProcess implements AOContract { }); // check the result of the send interaction - const output = await result({ + const output = await this.ao.result({ message: messageId, process: this.processId, }); @@ -161,24 +161,19 @@ export class AOProcess implements AOContract { const tagsOutput = output.Messages[0].Tags; const error = tagsOutput.find((tag) => tag.name === 'Error'); - // if there's an Error tag + // if there's an Error tag, throw an error related to it if (error) { - // parse the data const result = output.Messages[0].Data; throw new Error(`${error.Value}: ${result}`); } const resultData: K = JSON.parse(output.Messages[0].Data); - // console.log(result); - - // if (result.Error !== undefined) { - // throw new Error(result.Error); - // } - - // this.logger.debug(`Send interaction result`, { - // result, - // }); + this.logger.debug('Message result data', { + resultData, + messageId, + processId: this.processId, + }); return { id: messageId, result: resultData }; } From e5b5603ed9b6eaae3e6cc0b4f6407f91081ea272 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 17:24:20 -0600 Subject: [PATCH 13/29] fix(ao): add AR-IO-SDK tag to process interaction --- src/common/contracts/ao-process.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/contracts/ao-process.ts b/src/common/contracts/ao-process.ts index 621586a6..c1e667e4 100644 --- a/src/common/contracts/ao-process.ts +++ b/src/common/contracts/ao-process.ts @@ -18,6 +18,7 @@ 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 { @@ -130,9 +131,12 @@ export class AOProcess implements AOContract { processId: this.processId, }); + // append ar-io-sdk tags + const messageId = await this.ao.message({ process: this.processId, - tags, + // 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), }); From f07ac369045a0c71db50e9864f4b513d18a671b1 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 17:44:02 -0600 Subject: [PATCH 14/29] fix(ao): update APIs for ao interface to be more descriptive --- src/common/io.ts | 204 ++++++++++++++++++++++++++++++----------------- src/io.ts | 29 +++---- 2 files changed, 143 insertions(+), 90 deletions(-) diff --git a/src/common/io.ts b/src/common/io.ts index 5767c325..37460e5d 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -102,14 +102,14 @@ export class IOReadable implements AoIORead { } async getArNSRecord({ - domain, + name, }: { - domain: string; + name: string; }): Promise { return this.process.read({ tags: [ { name: 'Action', value: 'Record' }, - { name: 'Name', value: domain }, + { name: 'Name', value: name }, ], }); } @@ -129,14 +129,14 @@ export class IOReadable implements AoIORead { } async getArNSReservedName({ - domain, + name, }: { - domain: string; + name: string; }): Promise { return this.process.read({ tags: [ { name: 'Action', value: 'ReservedName' }, - { name: 'Name', value: domain }, + { name: 'Name', value: name }, ], }); } @@ -263,7 +263,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { } async joinNetwork({ - qty, + operatorStake, allowDelegatedStaking, delegateRewardShareRatio, fqdn, @@ -274,25 +274,64 @@ export class IOWriteable extends IOReadable implements AoIOWrite { properties, protocol, autoStake, - observerWallet, - }: JoinNetworkParams): Promise { + observerAddress, + }: Omit & { + observerAddress: string; + operatorStake: number | mIOToken; + }): Promise { return this.process.send({ signer: this.signer, - tags: [{ name: 'Action', value: 'JoinNetwork' }], - data: { - qty: qty.valueOf(), - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake: minDelegatedStake.valueOf(), - note, - port, - properties, - protocol, - autoStake, - observerWallet, - }, + tags: [ + { name: 'Action', value: 'JoinNetwork' }, + { + name: 'Quantity', + value: operatorStake.valueOf().toString(), + }, + { + name: 'AllowDelegatedStaking', + value: allowDelegatedStaking.toString(), + }, + { + name: 'DelegateRewardShareRatio', + value: delegateRewardShareRatio.toString(), + }, + { + name: 'FQDN', + value: fqdn, + }, + { + name: 'Label', + value: label, + }, + { + name: 'MinDelegatedStake', + value: minDelegatedStake.valueOf().toString(), + }, + { + name: 'Note', + value: note, + }, + { + name: 'Port', + value: port.toString(), + }, + { + name: 'Properties', + value: properties, + }, + { + name: 'Protocol', + value: protocol, + }, + { + name: 'AutoStake', + value: autoStake.toString(), + }, + { + name: 'ObserverAddress', + value: observerAddress, + }, + ], }); } @@ -307,68 +346,85 @@ export class IOWriteable extends IOReadable implements AoIOWrite { properties, protocol, autoStake, - observerWallet, - }: UpdateGatewaySettingsParams): Promise { + observerAddress, + }: Omit & { + observerAddress: string; + }): Promise { + // only include the tag if the value is not undefined + const allTags = [ + { name: 'Action', value: 'UpdateGatewaySettings' }, + { name: 'Label', value: label }, + { name: 'Note', value: note }, + { name: 'FQDN', value: fqdn }, + { name: 'Port', value: port?.toString() }, + { name: 'Properties', value: properties }, + { name: 'Protocol', value: protocol }, + { name: 'ObserverAddress', value: observerAddress }, + { + name: 'AllowDelegatedStaking', + value: allowDelegatedStaking?.toString(), + }, + { + name: 'DelegateRewardShareRatio', + value: delegateRewardShareRatio?.toString(), + }, + { + name: 'MinDelegatedStake', + value: minDelegatedStake?.valueOf().toString(), + }, + { name: 'AutoStake', value: autoStake?.toString() }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); + return this.process.send({ signer: this.signer, - tags: [{ name: 'Action', value: 'UpdateGatewaySettings' }], - data: { - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake: minDelegatedStake?.valueOf(), - note, - port, - properties, - protocol, - autoStake, - observerWallet, - }, + tags: prunedTags, }); } async increaseDelegateStake(params: { target: string; - qty: number | mIOToken; + increaseQty: number | mIOToken; }): Promise { return this.process.send({ signer: this.signer, tags: [ { name: 'Action', value: 'DelegateStake' }, { name: 'Target', value: params.target }, - { name: 'Quantity', value: params.qty.valueOf().toString() }, + { name: 'Quantity', value: params.increaseQty.valueOf().toString() }, ], - data: { - target: params.target, - qty: params.qty.valueOf(), - }, }); } async decreaseDelegateStake(params: { target: string; - qty: number | mIOToken; + decreaseQty: number | mIOToken; }): Promise { return this.process.send({ signer: this.signer, - tags: [{ name: 'Action', value: 'DecreaseDelegateStake' }], - data: { - target: params.target, - qty: params.qty.valueOf(), - }, + tags: [ + { name: 'Action', value: 'DecreaseDelegateStake' }, + { name: 'Target', value: params.target }, + { name: 'Quantity', value: params.decreaseQty.valueOf().toString() }, + ], }); } async increaseOperatorStake(params: { - qty: number | mIOToken; + increaseQty: number | mIOToken; }): Promise { return this.process.send({ signer: this.signer, - tags: [{ name: 'Action', value: 'IncreaseOperatorStake' }], - data: { - qty: params.qty.valueOf(), - }, + tags: [ + { name: 'Action', value: 'IncreaseOperatorStake' }, + { name: 'Quantity', value: params.increaseQty.valueOf().toString() }, + ], }); } @@ -377,10 +433,10 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }): Promise { return this.process.send({ signer: this.signer, - tags: [{ name: 'Action', value: 'DecreaseOperatorStake' }], - data: { - qty: params.qty.valueOf(), - }, + tags: [ + { name: 'Action', value: 'DecreaseOperatorStake' }, + { name: 'Quantity', value: params.qty.valueOf().toString() }, + ], }); } @@ -405,30 +461,30 @@ export class IOWriteable extends IOReadable implements AoIOWrite { } async extendLease(params: { - domain: string; + name: string; years: number; }): Promise { return this.process.send({ signer: this.signer, - tags: [{ name: 'Action', value: 'ExtendLease' }], - data: { - name: params.domain, - years: params.years, - }, + tags: [ + { name: 'Action', value: 'ExtendLease' }, + { name: 'Name', value: params.name }, + { name: 'Years', value: params.years.toString() }, + ], }); } async increaseUndernameLimit(params: { - domain: string; - qty: number; + name: string; + increaseCount: number; }): Promise { return this.process.send({ signer: this.signer, - tags: [{ name: 'Action', value: 'IncreaseUndernameLimit' }], - data: { - name: params.domain, - qty: params.qty, - }, + tags: [ + { name: 'Action', value: 'IncreaseUndernameLimit' }, + { name: 'Name', value: params.name }, + { name: 'Quantity', value: params.increaseCount.toString() }, + ], }); } } diff --git a/src/io.ts b/src/io.ts index 7a81b031..06c329d0 100644 --- a/src/io.ts +++ b/src/io.ts @@ -46,11 +46,7 @@ export interface AoIORead { >; getBalance(params: { address: WalletAddress }): Promise; getBalances(): Promise | Record>; - getArNSRecord({ - domain, - }: { - domain: string; - }): Promise; + getArNSRecord({ name }: { name: string }): Promise; getArNSRecords(): Promise< Record | Record >; @@ -58,9 +54,9 @@ export interface AoIORead { Record | Record >; getArNSReservedName({ - domain, + name, }: { - domain: string; + name: string; }): Promise; getEpoch({ blockHeight, @@ -93,7 +89,7 @@ export interface AoIOWrite extends AoIORead { ): Promise; joinNetwork( { - qty, + operatorStake, allowDelegatedStaking, delegateRewardShareRatio, fqdn, @@ -105,8 +101,9 @@ export interface AoIOWrite extends AoIORead { protocol, autoStake, observerAddress, - }: Omit & { - observerAddress?: WalletAddress; + }: Omit & { + observerAddress: string; + operatorStake: number | mIOToken; }, options?: WriteOptions, ): Promise; @@ -130,7 +127,7 @@ export interface AoIOWrite extends AoIORead { ): Promise; increaseOperatorStake( params: { - qty: number | mIOToken; + increaseQty: number | mIOToken; }, options?: WriteOptions, ): Promise; @@ -143,14 +140,14 @@ export interface AoIOWrite extends AoIORead { increaseDelegateStake( params: { target: WalletAddress; - qty: number | mIOToken; + increaseQty: number | mIOToken; }, options?: WriteOptions, ): Promise; decreaseDelegateStake( params: { target: WalletAddress; - qty: number | mIOToken; + decreaseQty: number | mIOToken; }, options?: WriteOptions, ): Promise; @@ -163,15 +160,15 @@ export interface AoIOWrite extends AoIORead { ): Promise; extendLease( params: { - domain: string; + name: string; years: number; }, options?: WriteOptions, ): Promise; increaseUndernameLimit( params: { - domain: string; - qty: number; + name: string; + increaseCount: number; }, options?: WriteOptions, ): Promise; From ddc4c1041ecbb316ff555e354b8c28782e859c3b Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 18:00:40 -0600 Subject: [PATCH 15/29] fix(ao): update epoch interfaces to support various inputs --- src/common/io.ts | 126 ++++++++++++++++++++++++++++++++++++++++------- src/io.ts | 30 +++++------ 2 files changed, 125 insertions(+), 31 deletions(-) diff --git a/src/common/io.ts b/src/common/io.ts index 37460e5d..9c207f60 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -22,7 +22,13 @@ import { Observations, WeightedObserver, } from '../contract-state.js'; -import { AoEpochData, AoGateway, AoIORead, AoIOWrite } from '../io.js'; +import { + AoEpochData, + AoGateway, + AoIORead, + AoIOWrite, + EpochInput, +} from '../io.js'; import { mIOToken } from '../token.js'; import { AoMessageResult, @@ -88,16 +94,32 @@ export class IOReadable implements AoIORead { } } - async getEpoch({ - blockHeight, - }: { - blockHeight: number; - }): Promise { - return this.process.read({ - tags: [ - { name: 'Action', value: 'Epoch' }, - { name: 'BlockHeight', value: blockHeight.toString() }, - ], + async getEpoch(epoch?: EpochInput): Promise { + const allTags = [ + { name: 'Action', value: 'Epoch' }, + { + name: 'Timestamp', + value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + }, + { + name: 'BlockHeight', + value: (epoch as { blockHeight?: number })?.blockHeight?.toString(), + }, + { + name: 'EpochIndex', + value: (epoch as { epochIndex?: number })?.epochIndex?.toString(), + }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); + + return this.process.read({ + tags: prunedTags, }); } @@ -186,21 +208,91 @@ export class IOReadable implements AoIORead { }); } - async getPrescribedObservers(): Promise { + async getPrescribedObservers( + epoch?: EpochInput, + ): Promise { + const allTags = [ + { name: 'Action', value: 'EpochPrescribedObservers' }, + { + name: 'Timestamp', + value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + }, + { + name: 'BlockHeight', + value: (epoch as { blockHeight?: number })?.blockHeight?.toString(), + }, + { + name: 'EpochIndex', + value: (epoch as { epochIndex?: number })?.epochIndex?.toString(), + }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); + return this.process.read({ - tags: [{ name: 'Action', value: 'PrescribedObservers' }], + tags: prunedTags, }); } - async getObservations(): Promise { + async getObservations(epoch?: EpochInput): Promise { + const allTags = [ + { name: 'Action', value: 'EpochObservations' }, + { + name: 'Timestamp', + value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + }, + { + name: 'BlockHeight', + value: (epoch as { blockHeight?: number })?.blockHeight?.toString(), + }, + { + name: 'EpochIndex', + value: (epoch as { epochIndex?: number })?.epochIndex?.toString(), + }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); + return this.process.read({ - tags: [{ name: 'Action', value: 'Observations' }], + tags: prunedTags, }); } - async getDistributions(): Promise { + async getDistributions(epoch?: EpochInput): Promise { + const allTags = [ + { name: 'Action', value: 'EpochDistributions' }, + { + name: 'Timestamp', + value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + }, + { + name: 'BlockHeight', + value: (epoch as { blockHeight?: number })?.blockHeight?.toString(), + }, + { + name: 'EpochIndex', + value: (epoch as { epochIndex?: number })?.epochIndex?.toString(), + }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); return this.process.read({ - tags: [{ name: 'Action', value: 'Distributions' }], + tags: prunedTags, }); } } diff --git a/src/io.ts b/src/io.ts index 06c329d0..b042111d 100644 --- a/src/io.ts +++ b/src/io.ts @@ -35,6 +35,18 @@ import { WriteOptions, } from './types.js'; +export type EpochInput = + | { + blockHeight: number; + } + | { + epochIndex: number; + } + | { + timestamp: number; + } + | undefined; + export interface AoIORead { getGateway({ address, @@ -58,21 +70,11 @@ export interface AoIORead { }: { name: string; }): Promise; - getEpoch({ - blockHeight, - }: { - blockHeight: number; - }): Promise; + getEpoch(epoch?: EpochInput): Promise; getCurrentEpoch(): Promise; - getPrescribedObservers(): Promise; - // TODO: allow timestamp or empty - getObservations({ blockHeight }): Promise; - // TODO: allow timestamp or empty - getDistributions({ - blockHeight, - }: { - blockHeight: number; - }): Promise; + getPrescribedObservers(epoch?: EpochInput): Promise; + getObservations(epoch?: EpochInput): Promise; + getDistributions(epoch?: EpochInput): Promise; } export interface AoIOWrite extends AoIORead { From 427e8baf8c8e58dffbfb5632ddb3c5d9c51d66e8 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 18:01:55 -0600 Subject: [PATCH 16/29] fix(ao): update stake interface --- src/common/io.ts | 6 +++--- src/io.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/io.ts b/src/common/io.ts index 9c207f60..03413b4c 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -480,16 +480,16 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async increaseDelegateStake(params: { + async delegateStake(params: { target: string; - increaseQty: number | mIOToken; + stakeQty: number | mIOToken; }): Promise { return this.process.send({ signer: this.signer, tags: [ { name: 'Action', value: 'DelegateStake' }, { name: 'Target', value: params.target }, - { name: 'Quantity', value: params.increaseQty.valueOf().toString() }, + { name: 'Quantity', value: params.stakeQty.valueOf().toString() }, ], }); } diff --git a/src/io.ts b/src/io.ts index b042111d..51ef8302 100644 --- a/src/io.ts +++ b/src/io.ts @@ -139,10 +139,10 @@ export interface AoIOWrite extends AoIORead { }, options?: WriteOptions, ): Promise; - increaseDelegateStake( + delegateStake( params: { target: WalletAddress; - increaseQty: number | mIOToken; + stakeQty: number | mIOToken; }, options?: WriteOptions, ): Promise; From 1dae85c067ae1fdc81ccab34a9da64aa6955a7b8 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 18:05:59 -0600 Subject: [PATCH 17/29] chore(ao): consolidate interaface types --- src/common/io.ts | 4 ++-- src/io.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/io.ts b/src/common/io.ts index 03413b4c..14d46f68 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -521,13 +521,13 @@ export class IOWriteable extends IOReadable implements AoIOWrite { } async decreaseOperatorStake(params: { - qty: number | mIOToken; + decreaseQty: number | mIOToken; }): Promise { return this.process.send({ signer: this.signer, tags: [ { name: 'Action', value: 'DecreaseOperatorStake' }, - { name: 'Quantity', value: params.qty.valueOf().toString() }, + { name: 'Quantity', value: params.decreaseQty.valueOf().toString() }, ], }); } diff --git a/src/io.ts b/src/io.ts index 51ef8302..7846da75 100644 --- a/src/io.ts +++ b/src/io.ts @@ -135,7 +135,7 @@ export interface AoIOWrite extends AoIORead { ): Promise; decreaseOperatorStake( params: { - qty: number | mIOToken; + decreaseQty: number | mIOToken; }, options?: WriteOptions, ): Promise; From 747fad28b64edbed288511a895af6b930c93f762 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 18:07:52 -0600 Subject: [PATCH 18/29] fix(ao): add getPrescribedNames for epoch api --- src/common/io.ts | 29 +++++++++++++++++++++++++++++ src/io.ts | 1 + 2 files changed, 30 insertions(+) diff --git a/src/common/io.ts b/src/common/io.ts index 14d46f68..9f4e0f8e 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -239,6 +239,35 @@ export class IOReadable implements AoIORead { }); } + async getPrescribedNames(epoch?: EpochInput): Promise { + const allTags = [ + { name: 'Action', value: 'EpochPrescribedNames' }, + { + name: 'Timestamp', + value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + }, + { + name: 'BlockHeight', + value: (epoch as { blockHeight?: number })?.blockHeight?.toString(), + }, + { + name: 'EpochIndex', + value: (epoch as { epochIndex?: number })?.epochIndex?.toString(), + }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); + + return this.process.read({ + tags: prunedTags, + }); + } + async getObservations(epoch?: EpochInput): Promise { const allTags = [ { name: 'Action', value: 'EpochObservations' }, diff --git a/src/io.ts b/src/io.ts index 7846da75..8b4a78b8 100644 --- a/src/io.ts +++ b/src/io.ts @@ -73,6 +73,7 @@ export interface AoIORead { getEpoch(epoch?: EpochInput): Promise; getCurrentEpoch(): Promise; getPrescribedObservers(epoch?: EpochInput): Promise; + getPrescribedNames(epoch?: EpochInput): Promise; getObservations(epoch?: EpochInput): Promise; getDistributions(epoch?: EpochInput): Promise; } From bf9cff7f7b570a152ae168cef693d324772b9b9d Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 4 Jun 2024 19:04:32 -0600 Subject: [PATCH 19/29] chore(test): fix docker setup --- docker-compose.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index 5fcba654..7cb75795 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,6 +19,9 @@ services: - GATEWAY_HOST=arlocal - GATEWAY_PORT=1984 - GATEWAY_PROTOCOL=http + - WARP_GATEWAY_HOST=arlocal + - WARP_GATEWAY_PORT=1984 + - WARP_GATEWAY_PROTOCOL=http - PREFETCH_CONTRACTS=false - BOOTSTRAP_CONTRACTS=false healthcheck: From 30d5e74384d6af25805fc5d1c35f30486ea204a2 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Wed, 5 Jun 2024 09:46:48 -0600 Subject: [PATCH 20/29] fix(io): add buyRecord API --- src/common/io.ts | 25 +++++++++++++++++++++++++ src/io.ts | 8 ++++++++ 2 files changed, 33 insertions(+) diff --git a/src/common/io.ts b/src/common/io.ts index 9f4e0f8e..7e921c83 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -581,6 +581,31 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } + async buyRecord(params: { + name: string; + years?: number; + type: 'lease' | 'permabuy'; + }): Promise { + const allTags = [ + { name: 'Action', value: 'BuyRecord' }, + { name: 'Name', value: params.name }, + { name: 'Type', value: params.type }, + { name: 'Years', value: params.years?.toString() }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); + + return this.process.send({ + signer: this.signer, + tags: prunedTags, + }); + } + async extendLease(params: { name: string; years: number; diff --git a/src/io.ts b/src/io.ts index 8b4a78b8..37db0a43 100644 --- a/src/io.ts +++ b/src/io.ts @@ -161,6 +161,14 @@ export interface AoIOWrite extends AoIORead { }, options?: WriteOptions, ): Promise; + buyRecord( + params: { + name: string; + years?: number; + type: 'lease' | 'permabuy'; + }, + options?: WriteOptions, + ): Promise; extendLease( params: { name: string; From 2262f76838743928eee0eb6551a6461284e5dd83 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Wed, 5 Jun 2024 10:26:06 -0600 Subject: [PATCH 21/29] chore: revert previous smartweave implementations --- src/common.ts | 23 +++++------------------ src/common/ar-io.ts | 11 ++++------- src/io.ts | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/common.ts b/src/common.ts index bfacd6fc..6594db18 100644 --- a/src/common.ts +++ b/src/common.ts @@ -22,6 +22,7 @@ import { ANTRecord, ANTState, AllowedProtocols, + ArIOState, ArNSAuctionData, ArNSNameData, ArNSReservedNameData, @@ -83,9 +84,8 @@ export type EvaluationParameters> = { } & T; export type ReadParameters = { - functionName?: string; + functionName: string; inputs?: Input; - tags?: Array<{ name: string; value: string }>; }; export type WriteOptions = { @@ -93,7 +93,7 @@ export type WriteOptions = { }; export type WriteParameters = WithSigner< - Required, 'tags'>> + Required> >; export interface BaseContract { @@ -108,19 +108,6 @@ export interface ReadContract { }: EvaluationParameters>): Promise; } -export interface AOContract { - read({ tags }): Promise; - send({ - tags, - data, - signer, - }: { - tags: { name: string; value: string }[]; - data: I; - signer: ContractSigner; - }): Promise<{ id: string; result?: K }>; -} - export interface WriteContract { writeInteraction( { functionName, inputs }: WriteParameters, @@ -129,7 +116,7 @@ export interface WriteContract { } // TODO: extend with additional methods -export interface ArIOReadContract extends BaseContract { +export interface ArIOReadContract extends BaseContract { getGateway({ address, evaluationOptions, @@ -202,7 +189,7 @@ export interface ArIOReadContract extends BaseContract { }>): Promise; } -export interface ArIOWriteContract extends ArIOReadContract { +export interface ArIOWriteContract extends ArIOReadContract { // write interactions transfer( { diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 5ec4ed32..8036a9f1 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -163,7 +163,7 @@ export class ArIO { } } -export class ArIOReadable implements ArIOReadContract { +export class ArIOReadable implements ArIOReadContract { protected contract: RemoteContract | WarpContract; constructor(config?: ContractConfiguration) { @@ -196,9 +196,9 @@ export class ArIOReadable implements ArIOReadContract { * arIO.getState({ evaluationOptions: { evalTo: { sortKey: 'mySortKey' } } }); * ``` */ - async getState(params: EvaluationParameters = {}): Promise { + async getState(params: EvaluationParameters = {}): Promise { const state = await this.contract.getState(params); - return state as T; + return state; } /** @@ -649,10 +649,7 @@ export class ArIOReadable implements ArIOReadContract { } } -export class ArIOWritable - extends ArIOReadable - implements ArIOWriteContract -{ +export class ArIOWritable extends ArIOReadable implements ArIOWriteContract { protected declare contract: WarpContract; private signer: ContractSigner; constructor({ diff --git a/src/io.ts b/src/io.ts index 37db0a43..71352ab0 100644 --- a/src/io.ts +++ b/src/io.ts @@ -28,6 +28,7 @@ import { import { mIOToken } from './token.js'; import { AoMessageResult, + ContractSigner, JoinNetworkParams, TransactionId, UpdateGatewaySettingsParams, @@ -47,6 +48,19 @@ export type EpochInput = } | undefined; +export interface AOContract { + read({ tags }): Promise; + send({ + tags, + data, + signer, + }: { + tags: { name: string; value: string }[]; + data: I; + signer: ContractSigner; + }): Promise<{ id: string; result?: K }>; +} + export interface AoIORead { getGateway({ address, From 31978f9788f67a488ddd1d0804c90809a10ac90c Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Wed, 5 Jun 2024 10:29:07 -0600 Subject: [PATCH 22/29] fix(ao): prune tags on joinNetwork --- src/common/io.ts | 111 +++++++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/src/common/io.ts b/src/common/io.ts index 7e921c83..eb0199ff 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -400,59 +400,68 @@ export class IOWriteable extends IOReadable implements AoIOWrite { observerAddress: string; operatorStake: number | mIOToken; }): Promise { + const allTags = [ + { name: 'Action', value: 'JoinNetwork' }, + { + name: 'Quantity', + value: operatorStake.valueOf().toString(), + }, + { + name: 'AllowDelegatedStaking', + value: allowDelegatedStaking.toString(), + }, + { + name: 'DelegateRewardShareRatio', + value: delegateRewardShareRatio.toString(), + }, + { + name: 'FQDN', + value: fqdn, + }, + { + name: 'Label', + value: label, + }, + { + name: 'MinDelegatedStake', + value: minDelegatedStake.valueOf().toString(), + }, + { + name: 'Note', + value: note, + }, + { + name: 'Port', + value: port.toString(), + }, + { + name: 'Properties', + value: properties, + }, + { + name: 'Protocol', + value: protocol, + }, + { + name: 'AutoStake', + value: autoStake.toString(), + }, + { + name: 'ObserverAddress', + value: observerAddress, + }, + ]; + + const prunedTags: { name: string; value: string }[] = allTags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); + return this.process.send({ signer: this.signer, - tags: [ - { name: 'Action', value: 'JoinNetwork' }, - { - name: 'Quantity', - value: operatorStake.valueOf().toString(), - }, - { - name: 'AllowDelegatedStaking', - value: allowDelegatedStaking.toString(), - }, - { - name: 'DelegateRewardShareRatio', - value: delegateRewardShareRatio.toString(), - }, - { - name: 'FQDN', - value: fqdn, - }, - { - name: 'Label', - value: label, - }, - { - name: 'MinDelegatedStake', - value: minDelegatedStake.valueOf().toString(), - }, - { - name: 'Note', - value: note, - }, - { - name: 'Port', - value: port.toString(), - }, - { - name: 'Properties', - value: properties, - }, - { - name: 'Protocol', - value: protocol, - }, - { - name: 'AutoStake', - value: autoStake.toString(), - }, - { - name: 'ObserverAddress', - value: observerAddress, - }, - ], + tags: prunedTags, }); } From 556f5d5d957a07944f3655f3ed1be026de51102e Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Wed, 5 Jun 2024 14:23:10 -0600 Subject: [PATCH 23/29] fix(ao): fix tag for join network, update observation response --- src/common/io.ts | 66 +++++++++++++++++++++++++++++++++++++++++++----- src/io.ts | 3 +-- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/common/io.ts b/src/common/io.ts index eb0199ff..38976b08 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -14,12 +14,14 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import Arweave from 'arweave'; + import { ioDevnetProcessId } from '../constants.js'; import { ArNSNameData, ArNSReservedNameData, EpochDistributionData, - Observations, + EpochObservations, WeightedObserver, } from '../contract-state.js'; import { @@ -60,6 +62,13 @@ export class IO { }: WithSigner<{ processId: string; }>): AoIOWrite; + static init({ + processId, + signer, + }: { + signer?: ContractSigner | undefined; + processId: string; + }); static init({ processId }: { processId: string }): AoIORead; static init( config?: OptionalSigner, @@ -77,8 +86,9 @@ export class IO { export class IOReadable implements AoIORead { protected process: AOProcess; + private arweave: Arweave; - constructor(config?: ProcessConfiguration) { + constructor(config?: ProcessConfiguration, arweave = Arweave.init({})) { if (!config) { this.process = new AOProcess({ processId: ioDevnetProcessId, @@ -92,12 +102,14 @@ export class IOReadable implements AoIORead { } else { throw new InvalidContractConfigurationError(); } + this.arweave = arweave; } async getEpoch(epoch?: EpochInput): Promise { const allTags = [ { name: 'Action', value: 'Epoch' }, { + // TODO: default this to the current network time name: 'Timestamp', value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', }, @@ -118,6 +130,14 @@ export class IOReadable implements AoIORead { }): tag is { name: string; value: string } => tag.value !== undefined, ); + // if it only contains the action, add default timestamp + if (prunedTags.length === 1) { + prunedTags.push({ + name: 'Timestamp', + value: (await this.arweave.blocks.getCurrent()).timestamp.toString(), + }); + } + return this.process.read({ tags: prunedTags, }); @@ -200,10 +220,12 @@ export class IOReadable implements AoIORead { } async getCurrentEpoch(): Promise { + const block = await this.arweave.blocks.getCurrent(); + const networkTimestamp = block.timestamp; return this.process.read({ tags: [ { name: 'Action', value: 'Epoch' }, - { name: 'Timestamp', value: `${Date.now()}` }, + { name: 'Timestamp', value: `${networkTimestamp}` }, ], }); } @@ -234,6 +256,14 @@ export class IOReadable implements AoIORead { }): tag is { name: string; value: string } => tag.value !== undefined, ); + // if it only contains the action, add default timestamp + if (prunedTags.length === 1) { + prunedTags.push({ + name: 'Timestamp', + value: (await this.arweave.blocks.getCurrent()).timestamp.toString(), + }); + } + return this.process.read({ tags: prunedTags, }); @@ -263,12 +293,20 @@ export class IOReadable implements AoIORead { }): tag is { name: string; value: string } => tag.value !== undefined, ); + // if it only contains the action, add default timestamp + if (prunedTags.length === 1) { + prunedTags.push({ + name: 'Timestamp', + value: (await this.arweave.blocks.getCurrent()).timestamp.toString(), + }); + } + return this.process.read({ tags: prunedTags, }); } - async getObservations(epoch?: EpochInput): Promise { + async getObservations(epoch?: EpochInput): Promise { const allTags = [ { name: 'Action', value: 'EpochObservations' }, { @@ -292,7 +330,15 @@ export class IOReadable implements AoIORead { }): tag is { name: string; value: string } => tag.value !== undefined, ); - return this.process.read({ + // if it only contains the action, add default timestamp + if (prunedTags.length === 1) { + prunedTags.push({ + name: 'Timestamp', + value: (await this.arweave.blocks.getCurrent()).timestamp.toString(), + }); + } + + return this.process.read({ tags: prunedTags, }); } @@ -320,6 +366,14 @@ export class IOReadable implements AoIORead { value: string | undefined; }): tag is { name: string; value: string } => tag.value !== undefined, ); + + // if it only contains the action, add default timestamp + if (prunedTags.length === 1) { + prunedTags.push({ + name: 'Timestamp', + value: (await this.arweave.blocks.getCurrent()).timestamp.toString(), + }); + } return this.process.read({ tags: prunedTags, }); @@ -403,7 +457,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { const allTags = [ { name: 'Action', value: 'JoinNetwork' }, { - name: 'Quantity', + name: 'OperatorStake', value: operatorStake.valueOf().toString(), }, { diff --git a/src/io.ts b/src/io.ts index 71352ab0..d26b2cd9 100644 --- a/src/io.ts +++ b/src/io.ts @@ -21,7 +21,6 @@ import { EpochObservations, GatewayDelegate, GatewaySettings, - Observations, VaultData, WeightedObserver, } from './contract-state.js'; @@ -88,7 +87,7 @@ export interface AoIORead { getCurrentEpoch(): Promise; getPrescribedObservers(epoch?: EpochInput): Promise; getPrescribedNames(epoch?: EpochInput): Promise; - getObservations(epoch?: EpochInput): Promise; + getObservations(epoch?: EpochInput): Promise; getDistributions(epoch?: EpochInput): Promise; } From b768dac72bdd6670c19fdcc42535a367c9220f80 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Wed, 5 Jun 2024 16:58:15 -0600 Subject: [PATCH 24/29] chore(types): organize types --- src/common.ts | 10 +--------- src/common/io.ts | 22 +++++++++++----------- src/io.ts | 26 ++++++++++++++++++++++++++ src/utils/smartweave.ts | 18 +----------------- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/common.ts b/src/common.ts index 6594db18..b03926f9 100644 --- a/src/common.ts +++ b/src/common.ts @@ -17,7 +17,7 @@ import { ArconnectSigner, ArweaveSigner } from 'arbundles'; import { GQLNodeInterface, Transaction } from 'warp-contracts'; -import { AOProcess, RemoteContract, WarpContract } from './common/index.js'; +import { RemoteContract, WarpContract } from './common/index.js'; import { ANTRecord, ANTState, @@ -65,14 +65,6 @@ export type ContractConfiguration> = contractTxId?: string; }; -export type ProcessConfiguration = - | { - process?: AOProcess; - } - | { - processId?: string; - }; - export type EvaluationOptions = { evalTo?: { sortKey: SortKey } | { blockHeight: BlockHeight }; // TODO: any other evaluation constraints diff --git a/src/common/io.ts b/src/common/io.ts index 38976b08..0687a2c4 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -30,6 +30,8 @@ import { AoIORead, AoIOWrite, EpochInput, + isProcessConfiguration, + isProcessIdConfiguration, } from '../io.js'; import { mIOToken } from '../token.js'; import { @@ -43,12 +45,9 @@ import { WalletAddress, WithSigner, } from '../types.js'; -import { - isProcessConfiguration, - isProcessIdConfiguration, -} from '../utils/smartweave.js'; import { AOProcess } from './contracts/ao-process.js'; import { InvalidContractConfigurationError } from './error.js'; +import { DefaultLogger } from './logger.js'; export class IO { static init(): AoIORead; @@ -98,6 +97,9 @@ export class IOReadable implements AoIORead { } else if (isProcessIdConfiguration(config)) { this.process = new AOProcess({ processId: config.processId, + logger: new DefaultLogger({ + level: 'info', + }), }); } else { throw new InvalidContractConfigurationError(); @@ -220,12 +222,10 @@ export class IOReadable implements AoIORead { } async getCurrentEpoch(): Promise { - const block = await this.arweave.blocks.getCurrent(); - const networkTimestamp = block.timestamp; return this.process.read({ tags: [ { name: 'Action', value: 'Epoch' }, - { name: 'Timestamp', value: `${networkTimestamp}` }, + { name: 'Timestamp', value: `${Date.now()}` }, ], }); } @@ -237,7 +237,7 @@ export class IOReadable implements AoIORead { { name: 'Action', value: 'EpochPrescribedObservers' }, { name: 'Timestamp', - value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + value: (epoch as { timestamp?: number }).timestamp?.toString(), }, { name: 'BlockHeight', @@ -260,7 +260,7 @@ export class IOReadable implements AoIORead { if (prunedTags.length === 1) { prunedTags.push({ name: 'Timestamp', - value: (await this.arweave.blocks.getCurrent()).timestamp.toString(), + value: `${Date.now()}`, }); } @@ -274,7 +274,7 @@ export class IOReadable implements AoIORead { { name: 'Action', value: 'EpochPrescribedNames' }, { name: 'Timestamp', - value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + value: (epoch as { timestamp?: number }).timestamp?.toString(), }, { name: 'BlockHeight', @@ -297,7 +297,7 @@ export class IOReadable implements AoIORead { if (prunedTags.length === 1) { prunedTags.push({ name: 'Timestamp', - value: (await this.arweave.blocks.getCurrent()).timestamp.toString(), + value: `${Date.now()}`, // TODO; replace with fetch the current network time }); } diff --git a/src/io.ts b/src/io.ts index d26b2cd9..aa92a744 100644 --- a/src/io.ts +++ b/src/io.ts @@ -14,6 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { AOProcess } from './common/index.js'; import { ArNSNameData, ArNSReservedNameData, @@ -34,6 +35,31 @@ import { WalletAddress, WriteOptions, } from './types.js'; +import { validateArweaveId } from './utils/arweave.js'; + +export function isProcessConfiguration( + config: object, +): config is { process: AOProcess } { + return 'process' in config; +} + +export function isProcessIdConfiguration( + config: object, +): config is { processId: string } { + return ( + 'processId' in config && + typeof config.processId === 'string' && + validateArweaveId(config.processId) === true + ); +} + +export type ProcessConfiguration = + | { + process?: AOProcess; + } + | { + processId?: string; + }; export type EpochInput = | { diff --git a/src/utils/smartweave.ts b/src/utils/smartweave.ts index e0fafc86..71c62a46 100644 --- a/src/utils/smartweave.ts +++ b/src/utils/smartweave.ts @@ -17,7 +17,7 @@ import Arweave from 'arweave'; import { EvaluationManifest } from 'warp-contracts'; -import { AOProcess, RemoteContract, WarpContract } from '../common/index.js'; +import { RemoteContract, WarpContract } from '../common/index.js'; import { SORT_KEY_REGEX } from '../constants.js'; import { SortKey } from '../types.js'; import { tagsToObject, validateArweaveId } from './arweave.js'; @@ -81,22 +81,6 @@ export function isContractConfiguration(config: object): config is { return 'contract' in config; } -export function isProcessConfiguration( - config: object, -): config is { process: AOProcess } { - return 'process' in config; -} - -export function isProcessIdConfiguration( - config: object, -): config is { processId: string } { - return ( - 'processId' in config && - typeof config.processId === 'string' && - validateArweaveId(config.processId) === true - ); -} - export function isContractTxIdConfiguration( config: object, ): config is { contractTxId: string } { From 723d92acf1221c9dab7b3c3cd831a0e56057f5f3 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Wed, 5 Jun 2024 17:00:50 -0600 Subject: [PATCH 25/29] chore(types): update legacy smartweave types --- src/common.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/common.ts b/src/common.ts index b03926f9..8d2fdef1 100644 --- a/src/common.ts +++ b/src/common.ts @@ -115,11 +115,9 @@ export interface ArIOReadContract extends BaseContract { }: EvaluationParameters<{ address: WalletAddress }>): Promise< Gateway | undefined >; - getGateways({ - evaluationOptions, - }: EvaluationParameters): Promise< - Record | Record - >; + getGateways( + params?: EvaluationParameters, + ): Promise | Record>; getBalance( params: { address: WalletAddress } & EvaluationOptions, ): Promise; From 67f8da987aa280e1648d4b65fa305f643ab42535 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Thu, 6 Jun 2024 14:35:32 -0600 Subject: [PATCH 26/29] fix(ao): support tags for all write interactions --- src/common/contracts/ao-process.ts | 2 +- src/common/io.ts | 214 +++++++++++++++++++---------- src/io.ts | 8 +- 3 files changed, 148 insertions(+), 76 deletions(-) diff --git a/src/common/contracts/ao-process.ts b/src/common/contracts/ao-process.ts index c1e667e4..1880dfce 100644 --- a/src/common/contracts/ao-process.ts +++ b/src/common/contracts/ao-process.ts @@ -143,7 +143,7 @@ export class AOProcess implements AOContract { this.logger.debug(`Sent message to process`, { messageId, - proceessId: this.processId, + processId: this.processId, }); // check the result of the send interaction diff --git a/src/common/io.ts b/src/common/io.ts index 0687a2c4..88123327 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -44,6 +44,7 @@ import { UpdateGatewaySettingsParams, WalletAddress, WithSigner, + WriteOptions, } from '../types.js'; import { AOProcess } from './contracts/ao-process.js'; import { InvalidContractConfigurationError } from './error.js'; @@ -311,7 +312,7 @@ export class IOReadable implements AoIORead { { name: 'Action', value: 'EpochObservations' }, { name: 'Timestamp', - value: (epoch as { timestamp?: number }).timestamp?.toString() ?? '', + value: (epoch as { timestamp?: number }).timestamp?.toString(), }, { name: 'BlockHeight', @@ -414,15 +415,20 @@ export class IOWriteable extends IOReadable implements AoIOWrite { } } - async transfer({ - target, - qty, - }: { - target: string; - qty: number | mIOToken; - }): Promise { + async transfer( + { + target, + qty, + }: { + target: string; + qty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ tags: [ + ...tags, { name: 'Action', value: 'Transfer' }, { name: 'Recipient', @@ -437,24 +443,29 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async joinNetwork({ - operatorStake, - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake, - note, - port, - properties, - protocol, - autoStake, - observerAddress, - }: Omit & { - observerAddress: string; - operatorStake: number | mIOToken; - }): Promise { + async joinNetwork( + { + operatorStake, + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerAddress, + }: Omit & { + observerAddress: string; + operatorStake: number | mIOToken; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; const allTags = [ + ...tags, { name: 'Action', value: 'JoinNetwork' }, { name: 'OperatorStake', @@ -519,23 +530,28 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async updateGatewaySettings({ - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake, - note, - port, - properties, - protocol, - autoStake, - observerAddress, - }: Omit & { - observerAddress: string; - }): Promise { + async updateGatewaySettings( + { + allowDelegatedStaking, + delegateRewardShareRatio, + fqdn, + label, + minDelegatedStake, + note, + port, + properties, + protocol, + autoStake, + observerAddress, + }: Omit & { + observerAddress: string; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; // only include the tag if the value is not undefined const allTags = [ + ...tags, { name: 'Action', value: 'UpdateGatewaySettings' }, { name: 'Label', value: label }, { name: 'Note', value: note }, @@ -572,13 +588,18 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async delegateStake(params: { - target: string; - stakeQty: number | mIOToken; - }): Promise { + async delegateStake( + params: { + target: string; + stakeQty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ signer: this.signer, tags: [ + ...tags, { name: 'Action', value: 'DelegateStake' }, { name: 'Target', value: params.target }, { name: 'Quantity', value: params.stakeQty.valueOf().toString() }, @@ -586,13 +607,18 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async decreaseDelegateStake(params: { - target: string; - decreaseQty: number | mIOToken; - }): Promise { + async decreaseDelegateStake( + params: { + target: string; + decreaseQty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ signer: this.signer, tags: [ + ...tags, { name: 'Action', value: 'DecreaseDelegateStake' }, { name: 'Target', value: params.target }, { name: 'Quantity', value: params.decreaseQty.valueOf().toString() }, @@ -600,56 +626,86 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async increaseOperatorStake(params: { - increaseQty: number | mIOToken; - }): Promise { + async increaseOperatorStake( + params: { + increaseQty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ signer: this.signer, tags: [ + ...tags, { name: 'Action', value: 'IncreaseOperatorStake' }, { name: 'Quantity', value: params.increaseQty.valueOf().toString() }, ], }); } - async decreaseOperatorStake(params: { - decreaseQty: number | mIOToken; - }): Promise { + async decreaseOperatorStake( + params: { + decreaseQty: number | mIOToken; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ signer: this.signer, tags: [ + ...tags, { name: 'Action', value: 'DecreaseOperatorStake' }, { name: 'Quantity', value: params.decreaseQty.valueOf().toString() }, ], }); } - async saveObservations(params: { - reportTxId: TransactionId; - failedGateways: WalletAddress[]; - }): Promise { + async saveObservations( + params: { + reportTxId: TransactionId; + failedGateways: WalletAddress[]; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send< { - observerReportTxId: TransactionId; + reportTxId: TransactionId; failedGateways: WalletAddress[]; }, never >({ signer: this.signer, - tags: [{ name: 'Action', value: 'SaveObservations' }], + tags: [ + ...tags, + { name: 'Action', value: 'SaveObservations' }, + { + name: 'ReportTxId', + value: params.reportTxId, + }, + { + name: 'FailedGateways', + value: params.failedGateways.join(','), + }, + ], data: { - observerReportTxId: params.reportTxId, + reportTxId: params.reportTxId, failedGateways: params.failedGateways, }, }); } - async buyRecord(params: { - name: string; - years?: number; - type: 'lease' | 'permabuy'; - }): Promise { + async buyRecord( + params: { + name: string; + years?: number; + type: 'lease' | 'permabuy'; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; const allTags = [ + ...tags, { name: 'Action', value: 'BuyRecord' }, { name: 'Name', value: params.name }, { name: 'Type', value: params.type }, @@ -669,13 +725,18 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async extendLease(params: { - name: string; - years: number; - }): Promise { + async extendLease( + params: { + name: string; + years: number; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ signer: this.signer, tags: [ + ...tags, { name: 'Action', value: 'ExtendLease' }, { name: 'Name', value: params.name }, { name: 'Years', value: params.years.toString() }, @@ -683,13 +744,18 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async increaseUndernameLimit(params: { - name: string; - increaseCount: number; - }): Promise { + async increaseUndernameLimit( + params: { + name: string; + increaseCount: number; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ signer: this.signer, tags: [ + ...tags, { name: 'Action', value: 'IncreaseUndernameLimit' }, { name: 'Name', value: params.name }, { name: 'Quantity', value: params.increaseCount.toString() }, diff --git a/src/io.ts b/src/io.ts index aa92a744..c2059f11 100644 --- a/src/io.ts +++ b/src/io.ts @@ -237,12 +237,18 @@ export interface AoIOState { export type AoEpochData = { epochIndex: number; + startHeight: number; observations: EpochObservations; prescribedObservers: WeightedObserver[]; startTimestamp: number; endTimestamp: number; distributionTimestamp: number; - distributions: Record; + distributions: { + rewards: Record; + distributedTimestamp: number; + totalDistributedRewards: number; + totalEligibleRewards: number; + }; }; export type AoGatewayStats = { From 8811016a9049102a0c5c3d9c82d473ccbe4e2d10 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Thu, 6 Jun 2024 15:34:08 -0600 Subject: [PATCH 27/29] fix(io): add init to provide custom process --- src/common/io.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/io.ts b/src/common/io.ts index 88123327..1f8320ea 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -52,6 +52,7 @@ import { DefaultLogger } from './logger.js'; export class IO { static init(): AoIORead; + static init({ process }: { process: AOProcess }): AoIORead; static init({ process, signer, @@ -549,7 +550,6 @@ export class IOWriteable extends IOReadable implements AoIOWrite { options?: WriteOptions, ): Promise { const { tags = [] } = options || {}; - // only include the tag if the value is not undefined const allTags = [ ...tags, { name: 'Action', value: 'UpdateGatewaySettings' }, From 5b87b9fc2c7ae71af6416d326b6a69c0b1eaad0f Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Thu, 6 Jun 2024 17:37:14 -0600 Subject: [PATCH 28/29] chore(examples): update esm examples --- examples/esm/index.mjs | 44 ++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index 368ceb74..ed7cc198 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -5,32 +5,26 @@ import fs from 'fs'; const wallet = JSON.parse(fs.readFileSync('./wallet.json', 'utf8')); const signer = new ArweaveSigner(wallet); const arIO = IO.init({ - processId: 'GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc', + processId: ioDevnetProcessId, signer, }); - // // testnet gateways - // const testnetGateways = await arIO.getGateways(); - // const protocolBalance = await arIO.getBalance({ - // address: ioDevnetProcessId, - // }); - // const ardriveRecord = await arIO.getArNSRecord({ domain: 'ardrive' }); - // const allRecords = await arIO.getArNSRecords(); - const transfer = await arIO.transfer({ - target: 'ZjmB2vEUlHlJ7-rgJkYP09N5IzLPhJyStVrK5u9dDEo', - qty: 1000000000000, + // devnet gateways + const testnetGateways = await arIO.getGateways(); + const protocolBalance = await arIO.getBalance({ + address: ioDevnetProcessId, }); - - console.log(transfer); - // console.dir( - // { - // testnetGateways, - // ardriveRecord, - // protocolBalance, - // arnsStats: { - // 'registered domains': Object.keys(allRecords).length, - // ardrive: allRecords.ardrive, - // }, - // }, - // { depth: 2 }, - // ); + const ardriveRecord = await arIO.getArNSRecord({ domain: 'ardrive' }); + const allRecords = await arIO.getArNSRecords(); + console.dir( + { + testnetGateways, + ardriveRecord, + protocolBalance, + arnsStats: { + 'registered domains': Object.keys(allRecords).length, + ardrive: allRecords.ardrive, + }, + }, + { depth: 2 }, + ); })(); From 9052151f0e100aa11127c874fbfdd56d006b37ff Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Thu, 6 Jun 2024 17:46:36 -0600 Subject: [PATCH 29/29] chore(example): update example and disable logs by default --- examples/esm/index.mjs | 19 +++++++++---------- src/common/contracts/ao-process.ts | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index ed7cc198..d60df79d 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -1,29 +1,28 @@ -import { ArweaveSigner, IO, ioDevnetProcessId } from '@ar.io/sdk'; -import fs from 'fs'; +import { IO, ioDevnetProcessId } from '@ar.io/sdk'; (async () => { - const wallet = JSON.parse(fs.readFileSync('./wallet.json', 'utf8')); - const signer = new ArweaveSigner(wallet); const arIO = IO.init({ processId: ioDevnetProcessId, - signer, }); // devnet gateways const testnetGateways = await arIO.getGateways(); const protocolBalance = await arIO.getBalance({ address: ioDevnetProcessId, }); - const ardriveRecord = await arIO.getArNSRecord({ domain: 'ardrive' }); + const ardriveRecord = await arIO.getArNSRecord({ name: 'ardrive' }); const allRecords = await arIO.getArNSRecords(); + const epoch = await arIO.getCurrentEpoch(); + const observations = await arIO.getObservations({ epochIndex: 19879 }); + const distributions = await arIO.getDistributions({ epochIndex: 19879 }); console.dir( { testnetGateways, ardriveRecord, + epoch, + observations, + distributions, protocolBalance, - arnsStats: { - 'registered domains': Object.keys(allRecords).length, - ardrive: allRecords.ardrive, - }, + names: Object.keys(allRecords), }, { depth: 2 }, ); diff --git a/src/common/contracts/ao-process.ts b/src/common/contracts/ao-process.ts index 1880dfce..f7b1037d 100644 --- a/src/common/contracts/ao-process.ts +++ b/src/common/contracts/ao-process.ts @@ -40,7 +40,7 @@ export class AOProcess implements AOContract { processId, // connectionConfig, // scheduler = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA', - logger = new DefaultLogger({ level: 'debug' }), + logger = new DefaultLogger({ level: 'info' }), }: { processId: string; scheduler?: string;