feat: allow for custom transactions signing
This commit is contained in:
@@ -56,7 +56,7 @@ describe('Testing the SmartWeave client for AssemblyScript WASM contract', () =>
|
||||
});
|
||||
|
||||
contract = smartweave.contract<ExampleContractState>(contractTxId).setEvaluationOptions({
|
||||
gasLimit: 14000000
|
||||
gasLimit: 1000000000
|
||||
});
|
||||
contract.connect(wallet);
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('Testing the SmartWeave client for AssemblyScript WASM contract', () =>
|
||||
});
|
||||
|
||||
expect(result.gasUsed).toBeGreaterThanOrEqual(12200000);
|
||||
expect(result.gasUsed).toBeLessThanOrEqual(12410898);
|
||||
expect(result.gasUsed).toBeLessThanOrEqual(20316175);
|
||||
});
|
||||
|
||||
it('should return stable gas results', async () => {
|
||||
@@ -124,7 +124,7 @@ describe('Testing the SmartWeave client for AssemblyScript WASM contract', () =>
|
||||
|
||||
results.forEach((result) => {
|
||||
expect(result.gasUsed).toBeGreaterThanOrEqual(12200000);
|
||||
expect(result.gasUsed).toBeLessThanOrEqual(12410898);
|
||||
expect(result.gasUsed).toBeLessThanOrEqual(20316175);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,10 +9,13 @@ import {
|
||||
Tags
|
||||
} from '@smartweave';
|
||||
import { NetworkInfoInterface } from 'arweave/node/network';
|
||||
import Transaction from 'arweave/node/lib/transaction';
|
||||
|
||||
export type CurrentTx = { interactionTxId: string; contractTxId: string };
|
||||
export type BenchmarkStats = { gatewayCommunication: number; stateEvaluation: number; total: number };
|
||||
|
||||
export type SigningFunction = (tx: Transaction) => Promise<void>;
|
||||
|
||||
/**
|
||||
* A base interface to be implemented by SmartWeave Contracts clients
|
||||
* - contains "low-level" methods that allow to interact with any contract
|
||||
@@ -24,14 +27,14 @@ export interface Contract<State = unknown> {
|
||||
txId(): string;
|
||||
|
||||
/**
|
||||
* Allows to connect {@link ArWallet} to a contract.
|
||||
* Connecting a wallet MAY be done before "viewState" (depending on contract implementation,
|
||||
* ie. whether called contract's function required "caller" info)
|
||||
* Connecting a wallet MUST be done before "writeInteraction".
|
||||
* Allows to connect a signer to a contract.
|
||||
* Connecting a signer MAY be done before "viewState" (depending on contract implementation,
|
||||
* i.e. whether called contract's function required "caller" info)
|
||||
* Connecting a signer MUST be done before "writeInteraction" and "bundleInteraction".
|
||||
*
|
||||
* @param wallet - {@link ArWallet} that will be connected to this contract
|
||||
* @param signer - either {@link ArWallet} that will be connected to this contract or custom {@link SigningFunction}
|
||||
*/
|
||||
connect(wallet: ArWallet): Contract<State>;
|
||||
connect(signer: ArWallet | SigningFunction): Contract<State>;
|
||||
|
||||
/**
|
||||
* Allows to set ({@link EvaluationOptions})
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
InteractionData,
|
||||
InteractionResult,
|
||||
LoggerFactory,
|
||||
SigningFunction,
|
||||
sleep,
|
||||
SmartWeave,
|
||||
SmartWeaveTags,
|
||||
@@ -34,6 +35,7 @@ import { TransactionStatusResponse } from 'arweave/node/transactions';
|
||||
import { NetworkInfoInterface } from 'arweave/node/network';
|
||||
import stringify from 'safe-stable-stringify';
|
||||
import * as crypto from 'crypto';
|
||||
import Transaction from 'arweave/node/lib/transaction';
|
||||
|
||||
/**
|
||||
* An implementation of {@link Contract} that is backwards compatible with current style
|
||||
@@ -67,7 +69,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
/**
|
||||
* wallet connected to this contract
|
||||
*/
|
||||
protected wallet?: ArWallet;
|
||||
protected signer?: SigningFunction;
|
||||
|
||||
constructor(
|
||||
private readonly _contractTxId: string,
|
||||
@@ -202,7 +204,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
strict = false
|
||||
): Promise<string | null> {
|
||||
this.logger.info('Write interaction input', input);
|
||||
if (!this.wallet) {
|
||||
if (!this.signer) {
|
||||
throw new Error("Wallet not connected. Use 'connect' method first.");
|
||||
}
|
||||
const { arweave } = this.smartweave;
|
||||
@@ -226,7 +228,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
|
||||
async bundleInteraction<Input>(input: Input, tags: Tags = [], strict = false): Promise<any | null> {
|
||||
this.logger.info('Bundle interaction input', input);
|
||||
if (!this.wallet) {
|
||||
if (!this.signer) {
|
||||
throw new Error("Wallet not connected. Use 'connect' method first.");
|
||||
}
|
||||
const interactionTx = await this.createInteraction(input, tags, emptyTransfer, strict);
|
||||
@@ -299,7 +301,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
|
||||
const interactionTx = await createTx(
|
||||
this.smartweave.arweave,
|
||||
this.wallet,
|
||||
this.signer,
|
||||
this._contractTxId,
|
||||
input,
|
||||
tags,
|
||||
@@ -321,8 +323,14 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
return this._networkInfo;
|
||||
}
|
||||
|
||||
connect(wallet: ArWallet): Contract<State> {
|
||||
this.wallet = wallet;
|
||||
connect(signer: ArWallet | SigningFunction): Contract<State> {
|
||||
if (typeof signer == 'function') {
|
||||
this.signer = signer;
|
||||
} else {
|
||||
this.signer = async (tx: Transaction) => {
|
||||
await this.smartweave.arweave.transactions.sign(tx, signer);
|
||||
};
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -526,7 +534,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
): Promise<InteractionResult<State, View>> {
|
||||
this.logger.info('Call contract input', input);
|
||||
this.maybeResetRootContract();
|
||||
if (!this.wallet) {
|
||||
if (!this.signer) {
|
||||
this.logger.warn('Wallet not set.');
|
||||
}
|
||||
const { arweave, stateEvaluator } = this.smartweave;
|
||||
@@ -547,7 +555,18 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
}
|
||||
|
||||
// add caller info to execution context
|
||||
const effectiveCaller = caller || (this.wallet ? await arweave.wallets.jwkToAddress(this.wallet) : '');
|
||||
let effectiveCaller;
|
||||
if (caller) {
|
||||
effectiveCaller = caller;
|
||||
} else if (this.signer) {
|
||||
const dummyTx = await arweave.createTransaction({ data: Math.random().toString().slice(-4) });
|
||||
await this.signer(dummyTx);
|
||||
effectiveCaller = await arweave.wallets.ownerToAddress(dummyTx.owner);
|
||||
} else {
|
||||
effectiveCaller = '';
|
||||
}
|
||||
|
||||
this.logger.info('effectiveCaller', effectiveCaller);
|
||||
executionContext = {
|
||||
...executionContext,
|
||||
caller: effectiveCaller
|
||||
@@ -565,7 +584,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
this.logger.debug('interaction', interaction);
|
||||
const tx = await createTx(
|
||||
arweave,
|
||||
this.wallet,
|
||||
this.signer,
|
||||
this._contractTxId,
|
||||
input,
|
||||
tags,
|
||||
|
||||
@@ -28,17 +28,17 @@ export class PstContractImpl extends HandlerBasedContract<PstState> implements P
|
||||
}
|
||||
|
||||
async saveNewSource(newContractSource: string): Promise<string | null> {
|
||||
if (!this.wallet) {
|
||||
if (!this.signer) {
|
||||
throw new Error("Wallet not connected. Use 'connect' method first.");
|
||||
}
|
||||
const { arweave } = this.smartweave;
|
||||
|
||||
const tx = await arweave.createTransaction({ data: newContractSource }, this.wallet);
|
||||
const tx = await arweave.createTransaction({ data: newContractSource });
|
||||
tx.addTag(SmartWeaveTags.APP_NAME, 'SmartWeaveContractSource');
|
||||
tx.addTag(SmartWeaveTags.APP_VERSION, '0.3.0');
|
||||
tx.addTag('Content-Type', 'application/javascript');
|
||||
|
||||
await arweave.transactions.sign(tx, this.wallet);
|
||||
await this.signer(tx);
|
||||
await arweave.transactions.post(tx);
|
||||
|
||||
return tx.id;
|
||||
|
||||
@@ -53,7 +53,7 @@ export class ContractHandlerApi<State> implements HandlerApi<State> {
|
||||
|
||||
const handlerResult = await Promise.race([timeoutPromise, this.contractFunction(stateCopy, interaction)]);
|
||||
|
||||
if (handlerResult && (handlerResult.state || handlerResult.result)) {
|
||||
if (handlerResult && (handlerResult.state !== undefined || handlerResult.result !== undefined)) {
|
||||
return {
|
||||
type: 'ok',
|
||||
result: handlerResult.result,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Arweave from 'arweave';
|
||||
import { ArWallet, GQLNodeInterface, GQLTagInterface, SmartWeaveTags } from '@smartweave';
|
||||
import { GQLNodeInterface, GQLTagInterface, SigningFunction, SmartWeaveTags } from '@smartweave';
|
||||
import Transaction from 'arweave/node/lib/transaction';
|
||||
import { CreateTransactionInterface } from 'arweave/node/common';
|
||||
import { BlockData } from 'arweave/node/blocks';
|
||||
|
||||
export async function createTx(
|
||||
arweave: Arweave,
|
||||
wallet: ArWallet,
|
||||
signer: SigningFunction,
|
||||
contractId: string,
|
||||
input: any,
|
||||
tags: { name: string; value: string }[],
|
||||
@@ -24,7 +24,7 @@ export async function createTx(
|
||||
}
|
||||
}
|
||||
|
||||
const interactionTx = await arweave.createTransaction(options, wallet);
|
||||
const interactionTx = await arweave.createTransaction(options);
|
||||
|
||||
if (!input) {
|
||||
throw new Error(`Input should be a truthy value: ${JSON.stringify(input)}`);
|
||||
@@ -42,8 +42,8 @@ export async function createTx(
|
||||
interactionTx.addTag(SmartWeaveTags.CONTRACT_TX_ID, contractId);
|
||||
interactionTx.addTag(SmartWeaveTags.INPUT, JSON.stringify(input));
|
||||
|
||||
if (wallet) {
|
||||
await arweave.transactions.sign(interactionTx, wallet);
|
||||
if (signer) {
|
||||
await signer(interactionTx);
|
||||
}
|
||||
return interactionTx;
|
||||
}
|
||||
|
||||
59
tools/separate-sign.ts
Normal file
59
tools/separate-sign.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable */
|
||||
import Arweave from 'arweave';
|
||||
import {LoggerFactory, SmartWeaveNodeFactory} from '../src';
|
||||
import {TsLogFactory} from '../src/logging/node/TsLogFactory';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {JWKInterface} from 'arweave/node/lib/wallet';
|
||||
|
||||
async function main() {
|
||||
let wallet: JWKInterface = readJSON('./.secrets/33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA.json');;
|
||||
LoggerFactory.INST.logLevel('error');
|
||||
LoggerFactory.INST.logLevel('info', 'custom-sign');
|
||||
LoggerFactory.INST.logLevel('debug', 'HandlerBasedContract');
|
||||
const logger = LoggerFactory.INST.create('custom-sign');
|
||||
|
||||
const arweave = Arweave.init({
|
||||
host: 'arweave.net',
|
||||
port: 443,
|
||||
protocol: 'https'
|
||||
});
|
||||
|
||||
try {
|
||||
const smartweave = SmartWeaveNodeFactory
|
||||
.memCachedBased(arweave)
|
||||
.useRedStoneGateway()
|
||||
.build();
|
||||
|
||||
let customSignCalled = false;
|
||||
|
||||
const result = await smartweave.contract("Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY")
|
||||
.connect(/*wallet*/async (tx) => {
|
||||
logger.info("Custom sign function");
|
||||
customSignCalled = true;
|
||||
const owner = wallet.n;
|
||||
logger.info("Owner:", owner);
|
||||
tx.setOwner(owner);
|
||||
})
|
||||
.viewState({function: "assetsLeft"});
|
||||
|
||||
logger.info("customSignCalled", customSignCalled);
|
||||
logger.info("result", result);
|
||||
|
||||
} catch (e) {
|
||||
logger.error(e)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function readJSON(path: string): JWKInterface {
|
||||
const content = fs.readFileSync(path, "utf-8");
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
} catch (e) {
|
||||
throw new Error(`File "${path}" does not contain a valid JSON`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => console.error(e));
|
||||
Reference in New Issue
Block a user