Skip to content

Commit

Permalink
chore(connect): update connect workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
Atticus committed Apr 5, 2024
1 parent 9fcf440 commit 68b555a
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 82 deletions.
16 changes: 7 additions & 9 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ArconnectSigner, ArweaveSigner } from 'arbundles';
import { DataItem } from 'warp-arbundles';
import { InteractionResult, Transaction } from 'warp-contracts';

import { RemoteContract, WarpContract } from './common/index.js';
import {
ANTRecord,
ANTState,
Expand All @@ -42,9 +43,7 @@ export type ContractConfiguration = {
signer?: ContractSigner; // TODO: optionally allow JWK in place of signer
} & (
| {
contract?:
| (BaseContract<unknown> & ReadContract)
| (BaseContract<unknown> & ReadWriteContract);
contract?: WarpContract<unknown> | RemoteContract<unknown>;
}
| {
contractTxId: string;
Expand All @@ -54,9 +53,7 @@ export type ContractConfiguration = {
export function isContractConfiguration<T>(
config: ContractConfiguration,
): config is {
contract:
| (BaseContract<T> & ReadContract)
| (BaseContract<T> & ReadWriteContract);
contract: WarpContract<T> | RemoteContract<T>;
} {
return 'contract' in config;
}
Expand Down Expand Up @@ -86,8 +83,6 @@ export type WriteParameters<Input> = {

export interface BaseContract<T> {
getState(params: EvaluationParameters): Promise<T>;
connect(signer: ContractSigner): this;
connected(): boolean;
}

export interface ReadContract {
Expand Down Expand Up @@ -123,7 +118,7 @@ export interface SmartWeaveContract<T> {
}

// TODO: extend with additional methods
export interface ArIOContract extends BaseContract<ArIOState> {
export interface ArIOReadContract extends BaseContract<ArIOState> {
getGateway({
address,
evaluationOptions,
Expand Down Expand Up @@ -185,6 +180,9 @@ export interface ArIOContract extends BaseContract<ArIOState> {
domain: string;
type?: RegistrationType;
}>): Promise<ArNSAuctionData>;
}

export interface ArIOWriteContract {
// write interactions
joinNetwork({
qty,
Expand Down
92 changes: 47 additions & 45 deletions src/common/ar-io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
*/
import { ARNS_TESTNET_REGISTRY_TX } from '../constants.js';
import {
ArIOContract,
ArIOReadContract,
ArIOState,
ArIOWriteContract,
ArNSAuctionData,
ArNSNameData,
BaseContract,
CONTRACT_FUNCTIONS,
ContractConfiguration,
ContractSigner,
Expand All @@ -30,22 +30,19 @@ import {
Gateway,
JoinNetworkParams,
Observations,
ReadContract,
RegistrationType,
UpdateGatewaySettingsParams,
WeightedObserver,
WriteContract,
WriteInteractionResult,
isContractConfiguration,
isContractTxIdConfiguration,
} from '../types.js';
import { mixInto } from '../utils/common.js';
import { RemoteContract } from './contracts/remote-contract.js';
import { InvalidSignerError, WarpContract } from './index.js';

export class ArIO implements ArIOContract, BaseContract<ArIOState> {
private contract: BaseContract<ArIOState> &
ReadContract &
Partial<WriteContract>;
export class ArIO implements ArIOReadContract {
private contract: RemoteContract<ArIOState> | WarpContract<ArIOState>;
private signer: ContractSigner | undefined;

constructor(
Expand All @@ -68,20 +65,6 @@ export class ArIO implements ArIOContract, BaseContract<ArIOState> {
}
}

connect(signer: ContractSigner): this {
this.signer = signer;
if (this.contract instanceof RemoteContract) {
const config = this.contract.configuration();
this.contract = new WarpContract<ArIOState>({
...config,
signer,
});
}
this.contract.connect(this.signer);

return this;
}

connected(): boolean {
return this.signer !== undefined;
}
Expand Down Expand Up @@ -260,84 +243,103 @@ export class ArIO implements ArIOContract, BaseContract<ArIOState> {

return auctions;
}
// write methods

isContractWritable(
contract: BaseContract<ArIOState>,
): contract is BaseContract<ArIOState> & WriteContract {
return 'writeInteraction' in contract && contract.connected();
connect(signer: ContractSigner): void {
let writeableContract: WarpContract<ArIOState> | undefined = undefined;

// create or set the writable contract to be used in our mixin class
if (this.contract instanceof RemoteContract) {
writeableContract = new WarpContract(this.contract.configuration());
} else if (this.contract instanceof WarpContract) {
writeableContract = this.contract;
}

if (!writeableContract) {
throw new InvalidSignerError();
}

mixInto(
this.contract,
new ArIOWritable({
contract: writeableContract,
signer,
}),
);
}
}

export class ArIOWritable implements ArIOWriteContract {
private contract: WarpContract<ArIOState>;
private signer: ContractSigner;
constructor({
contract,
signer,
}: {
contract: WarpContract<ArIOState>;
signer: ContractSigner;
}) {
this.contract = contract;
this.signer = signer;
}

async joinNetwork(
params: JoinNetworkParams,
): Promise<WriteInteractionResult> {
if (!this.isContractWritable(this.contract)) {
throw new InvalidSignerError();
}
return this.contract.writeInteraction({
functionName: 'joinNetwork',
inputs: params,
signer: this.signer,
});
}
async updateGatewaySettings(
params: UpdateGatewaySettingsParams,
): Promise<WriteInteractionResult> {
if (!this.isContractWritable(this.contract)) {
throw new InvalidSignerError();
}
return this.contract.writeInteraction({
functionName: 'updateGatewaySettings',
inputs: params,
signer: this.signer,
});
}

async increaseDelegateState(params: {
target: string;
qty: number;
}): Promise<WriteInteractionResult> {
if (!this.isContractWritable(this.contract)) {
throw new InvalidSignerError();
}
return this.contract.writeInteraction({
functionName: 'delegateState',
inputs: params,
signer: this.signer,
});
}

async decreaseDelegateState(params: {
target: string;
qty: number;
}): Promise<WriteInteractionResult> {
if (!this.isContractWritable(this.contract)) {
throw new InvalidSignerError();
}
return this.contract.writeInteraction({
functionName: 'decreaseDelegateState',
inputs: params,
signer: this.signer,
});
}

async increaseOperatorStake(params: {
qty: number;
}): Promise<WriteInteractionResult> {
if (!this.isContractWritable(this.contract)) {
throw new InvalidSignerError();
}
return this.contract.writeInteraction({
functionName: 'increaseOperatorStake',
inputs: params,
signer: this.signer,
});
}

async decreaseOperatorStake(params: {
qty: number;
}): Promise<WriteInteractionResult> {
if (!this.isContractWritable(this.contract)) {
throw new InvalidSignerError();
}
return this.contract.writeInteraction({
functionName: 'decreaseOperatorStake',
inputs: params,
signer: this.signer,
});
}
}
53 changes: 25 additions & 28 deletions src/common/contracts/warp-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ArconnectSigner } from 'arbundles';
import Arweave from 'arweave';
import { DataItem } from 'warp-arbundles';
import {
Expand Down Expand Up @@ -54,21 +55,19 @@ export class WarpContract<T>
private logger: Logger;
private warp: Warp;
// warp compatible signer that uses ContractSigner
private signer: CustomSignature | undefined;
//private signer: CustomSignature | undefined;

constructor({
contractTxId,
cacheUrl,
warp = defaultWarp,
signer,
logger = new DefaultLogger({
level: 'debug',
}),
}: {
contractTxId: string;
cacheUrl?: string;
warp?: Warp;
signer?: ContractSigner;
arweave?: Arweave;
logger?: Logger;
}) {
Expand All @@ -77,9 +76,6 @@ export class WarpContract<T>
this.cacheUrl = cacheUrl;
this.warp = warp;
this.logger = logger;
if (signer) {
this.connect(signer);
}
}

configuration(): { contractTxId: string; cacheUrl: string | undefined } {
Expand All @@ -89,7 +85,12 @@ export class WarpContract<T>
};
}

connect(signer: ContractSigner): this {
// TODO: could abstract into our own interface that constructs different signers
async createWarpSigner(signer: ContractSigner): Promise<CustomSignature> {
// ensure appropriate permissions are granted with injected signers.
if (signer.publicKey === undefined && signer instanceof ArconnectSigner) {
await signer.setPublicKey();
}
const warpSigner = new Signature(this.warp, {
signer: async (tx: Transaction) => {
const dataToSign = await tx.getSignatureData();
Expand All @@ -103,13 +104,9 @@ export class WarpContract<T>
},
type: 'arweave',
});
this.contract = this.contract.connect(warpSigner);
this.signer = warpSigner;
return this;
}

connected(): boolean {
return this.signer !== undefined;
//this.contract = this.contract.connect(warpSigner);
//this.signer = warpSigner;
return warpSigner;
}

async getState({ evaluationOptions = {} }: EvaluationParameters): Promise<T> {
Expand All @@ -130,7 +127,11 @@ export class WarpContract<T>
return evaluationResult.cachedValue.state as T;
}

async ensureContractInit(): Promise<void> {
async ensureContractInit({
signer,
}: {
signer?: ContractSigner;
} = {}): Promise<void> {
this.logger.debug(`Checking contract initialized`, {
contractTxId: this.contractTxId,
});
Expand All @@ -144,15 +145,15 @@ export class WarpContract<T>
contractTxId: this.contractTxId,
});
this.contract.setEvaluationOptions(evaluationOptions);
await this.syncState();
}

private async syncState() {
if (signer) this.contract.connect(await this.createWarpSigner(signer));

if (this.cacheUrl !== undefined) {
this.logger.debug(`Syncing contract state`, {
contractTxId: this.contractTxId,
remoteCacheUrl: this.cacheUrl,
});

await this.contract.syncState(
`${this.cacheUrl}/v1/contract/${this.contractTxId}`,
{
Expand All @@ -166,7 +167,7 @@ export class WarpContract<T>
functionName,
inputs,
// TODO: view state only supports sort key so we won't be able to use block height
}: EvaluationParameters<{ functionName: string; inputs: I }>): Promise<K> {
}: EvaluationParameters<{ functionName: string; inputs?: I }>): Promise<K> {
const evaluationResult = await this.contract.viewState<unknown, K>({
function: functionName,
...inputs,
Expand All @@ -191,20 +192,16 @@ export class WarpContract<T>
functionName,
inputs,
dryWrite = false,
}: EvaluationParameters<WriteParameters<Input>>): Promise<
Transaction | DataItem | InteractionResult<unknown, unknown>
> {
signer,
}: EvaluationParameters<WriteParameters<Input>> & {
signer: ContractSigner;
}): Promise<Transaction | DataItem | InteractionResult<unknown, unknown>> {
try {
if (!this.signer) {
throw new Error(
'Contract not connected - call .connect(signer) to connect a signer for write interactions ',
);
}
this.logger.debug(`Write interaction: ${functionName}`, {
contractTxId: this.contractTxId,
});
// Sync state before writing
await this.ensureContractInit();
await this.ensureContractInit({ signer });

// run dry write before actual write
const result = await this.contract.dryWrite<Input>({
Expand Down
Loading

0 comments on commit 68b555a

Please sign in to comment.