diff --git a/jest.config.ts b/jest.config.mjs
similarity index 82%
rename from jest.config.ts
rename to jest.config.mjs
index 03c33e1b..84cc60a5 100644
--- a/jest.config.ts
+++ b/jest.config.mjs
@@ -1,13 +1,14 @@
-module.exports = {
+export default {
preset: 'ts-jest',
clearMocks: true,
moduleFileExtensions: ['ts', 'js', 'mjs'],
- testMatch: ['**/src/**/*.test.ts', '**/tests/**/*.test.ts'],
+ testMatch: ['**/src/**/*.test.ts'],
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', 'tests/**/*.ts'],
testEnvironment: 'node',
testTimeout: 120_000,
extensionsToTreatAsEsm: ['.ts'],
+ passWithNoTests: true,
transform: {
'^.+\\.(ts|js)$': ['ts-jest', { useESM: true }],
},
diff --git a/jest.integration.config.ts b/jest.integration.config.mjs
similarity index 96%
rename from jest.integration.config.ts
rename to jest.integration.config.mjs
index aa601444..a1cd37a7 100644
--- a/jest.integration.config.ts
+++ b/jest.integration.config.mjs
@@ -1,4 +1,4 @@
-module.exports = {
+export default {
preset: 'ts-jest',
setupFiles: ['./tests/integration/jest.setup.ts'],
globalTeardown: './tests/integration/jest.teardown.ts',
diff --git a/package.json b/package.json
index 0001d826..5ac3537b 100644
--- a/package.json
+++ b/package.json
@@ -62,13 +62,14 @@
"build:esm": "yarn tsc -p tsconfig.json",
"build:cjs": "yarn tsc -p tsconfig.cjs.json && echo \"{\\\"type\\\": \\\"commonjs\\\"}\" > lib/cjs/package.json",
"build": "yarn clean && yarn build:web && yarn build:esm && yarn build:cjs",
- "clean": "rimraf [ lib coverage bundles ]",
+ "clean": "rimraf [ lib coverage bundles tests/contracts tests/wallets ]",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"format": "prettier --check .",
"format:fix": "prettier --write .",
- "test": "yarn clean && jest .",
- "test:integration": "docker compose up -d && yarn test --config=jest.integration.config.ts && docker compose down",
+ "test": "yarn test:unit && yarn test:integration",
+ "test:unit": "yarn clean && jest --config=jest.config.mjs",
+ "test:integration": "yarn clean && docker compose up -d && jest --config=jest.integration.config.mjs && docker compose down",
"prepare": "husky install",
"example:mjs": "yarn build:esm && node examples/node/index.mjs",
"example:cjs": "yarn build:cjs && node examples/node/index.cjs",
@@ -117,7 +118,7 @@
"axios": "1.4.0",
"setimmediate": "^1.0.5",
"warp-arbundles": "^1.0.4",
- "warp-contracts": "^1.4.38",
+ "warp-contracts": "1.4.39",
"warp-contracts-plugin-deploy": "^1.0.13",
"winston": "^3.11.0"
},
diff --git a/src/common/contracts/warp-contract.ts b/src/common/contracts/warp-contract.ts
index d561eb72..9341975c 100644
--- a/src/common/contracts/warp-contract.ts
+++ b/src/common/contracts/warp-contract.ts
@@ -15,18 +15,18 @@
* along with this program. If not, see .
*/
import Arweave from 'arweave';
-import { DataItem, Signer } from 'warp-arbundles';
+import { DataItem } from 'warp-arbundles';
import {
Contract,
+ CustomSignature,
InteractionResult,
LoggerFactory,
+ Signature,
Transaction,
Warp,
- WarpFactory,
- defaultCacheOptions,
} from 'warp-contracts';
-import { defaultArweave } from '../../constants.js';
+import { defaultWarp } from '../../constants.js';
import {
BaseContract,
ContractSigner,
@@ -36,6 +36,7 @@ import {
WriteContract,
WriteParameters,
} from '../../types.js';
+import { sha256B64Url, toB64Url } from '../../utils/base64.js';
import { getContractManifest } from '../../utils/smartweave.js';
import { FailedRequestError, WriteInteractionError } from '../error.js';
import { DefaultLogger } from '../logger.js';
@@ -50,23 +51,17 @@ export class WarpContract
private contract: Contract;
private contractTxId: string;
private cacheUrl: string | undefined;
- private arweave: Arweave;
- private log: Logger;
- private signer: ContractSigner | undefined;
+ private logger: Logger;
+ private warp: Warp;
+ // warp compatible signer that uses ContractSigner
+ private signer: CustomSignature | undefined;
constructor({
contractTxId,
cacheUrl,
- warp = WarpFactory.forMainnet(
- {
- ...defaultCacheOptions,
- inMemory: true, // default to in memory for now, a custom warp implementation can be provided
- },
- true,
- ),
+ warp = defaultWarp,
signer,
- arweave = defaultArweave,
- log = new DefaultLogger({
+ logger = new DefaultLogger({
level: 'debug',
}),
}: {
@@ -75,13 +70,13 @@ export class WarpContract
warp?: Warp;
signer?: ContractSigner;
arweave?: Arweave;
- log?: Logger;
+ logger?: Logger;
}) {
this.contractTxId = contractTxId;
- this.contract = warp.contract(contractTxId);
+ this.contract = warp.contract(contractTxId);
this.cacheUrl = cacheUrl;
- this.arweave = arweave;
- this.log = log;
+ this.warp = warp;
+ this.logger = logger;
if (signer) {
this.connect(signer);
}
@@ -94,13 +89,25 @@ export class WarpContract
};
}
- // base contract methods
connect(signer: ContractSigner) {
- // TODO: Update type to use Signer interface
- this.signer = signer;
- this.contract = this.contract.connect(signer as Signer);
+ const warpSigner = new Signature(this.warp, {
+ signer: async (tx: Transaction) => {
+ const dataToSign = await tx.getSignatureData();
+ const signatureBuffer = Buffer.from(await signer.sign(dataToSign));
+ const id = sha256B64Url(signatureBuffer);
+ tx.setSignature({
+ id: id,
+ owner: toB64Url(signer.publicKey),
+ signature: toB64Url(signatureBuffer),
+ });
+ },
+ type: 'arweave',
+ });
+ this.contract = this.contract.connect(warpSigner);
+ this.signer = warpSigner;
return this;
}
+
async getState({ evaluationOptions = {} }: EvaluationParameters): Promise {
await this.ensureContractInit();
const evalTo = evaluationOptions?.evalTo;
@@ -120,16 +127,16 @@ export class WarpContract
}
async ensureContractInit(): Promise {
- this.log.debug(`Checking contract initialized`, {
+ this.logger.debug(`Checking contract initialized`, {
contractTxId: this.contractTxId,
});
// Get contact manifest and sync state
- this.log.debug(`Fetching contract manifest`, {
+ this.logger.debug(`Fetching contract manifest`, {
contractTxId: this.contractTxId,
});
const { evaluationOptions = {} } = await getContractManifest({
- arweave: this.arweave,
+ arweave: this.warp.arweave,
contractTxId: this.contractTxId,
});
this.contract.setEvaluationOptions(evaluationOptions);
@@ -138,7 +145,7 @@ export class WarpContract
private async syncState() {
if (this.cacheUrl !== undefined) {
- this.log.debug(`Syncing contract state`, {
+ this.logger.debug(`Syncing contract state`, {
contractTxId: this.contractTxId,
remoteCacheUrl: this.cacheUrl,
});
@@ -179,7 +186,7 @@ export class WarpContract
async writeInteraction({
functionName,
inputs,
- dryWrite = true,
+ dryWrite = false,
}: EvaluationParameters>): Promise<
Transaction | DataItem | InteractionResult
> {
@@ -189,7 +196,7 @@ export class WarpContract
'Contract not connected - call .connect(signer) to connect a signer for write interactions ',
);
}
- this.log.debug(`Write interaction: ${functionName}`, {
+ this.logger.debug(`Write interaction: ${functionName}`, {
contractTxId: this.contractTxId,
});
// Sync state before writing
@@ -207,7 +214,7 @@ export class WarpContract
}
if (dryWrite) {
- this.log.debug(`Dry write interaction successful`, {
+ this.logger.debug(`Dry write interaction successful`, {
contractTxId: this.contractTxId,
functionName,
});
diff --git a/src/constants.ts b/src/constants.ts
index fee4d749..8a2b45f1 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
import Arweave from 'arweave';
+import { WarpFactory, defaultCacheOptions } from 'warp-contracts';
export const ARWEAVE_TX_REGEX = new RegExp('^[a-zA-Z0-9_-]{43}$');
// sortkey: padded blockheight to 12, JS timestamp, hash of transactionID + block hash. Timestamp only applicable to L2 and normally is all zeros.
@@ -32,3 +33,12 @@ export const defaultArweave = Arweave.init({
port: 443,
protocol: 'https',
});
+
+export const defaultWarp = WarpFactory.forMainnet(
+ {
+ ...defaultCacheOptions,
+ inMemory: true,
+ },
+ true,
+ defaultArweave,
+);
diff --git a/src/utils/base64.ts b/src/utils/base64.ts
new file mode 100644
index 00000000..8b00ac3b
--- /dev/null
+++ b/src/utils/base64.ts
@@ -0,0 +1,37 @@
+/**
+ * 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 { bufferTob64Url } from 'arweave/node/lib/utils.js';
+import { createHash } from 'crypto';
+
+export function fromB64Url(input: string): Buffer {
+ const paddingLength = input.length % 4 === 0 ? 0 : 4 - (input.length % 4);
+
+ const base64 = input
+ .replace(/-/g, '+')
+ .replace(/_/g, '/')
+ .concat('='.repeat(paddingLength));
+
+ return Buffer.from(base64, 'base64');
+}
+
+export function toB64Url(buffer: Buffer): string {
+ return bufferTob64Url(buffer);
+}
+
+export function sha256B64Url(input: Buffer): string {
+ return toB64Url(createHash('sha256').update(input).digest());
+}
diff --git a/tests/integration/ant.test.ts b/tests/integration/ant.test.ts
index 6b18200f..a1fbaa78 100644
--- a/tests/integration/ant.test.ts
+++ b/tests/integration/ant.test.ts
@@ -2,6 +2,7 @@ import { ArweaveSigner } from 'arbundles';
import { ANT } from '../../src/common/ant';
import { RemoteContract } from '../../src/common/contracts/remote-contract';
+import { DefaultLogger } from '../../src/common/logger';
import { ANTState } from '../../src/contract-state';
import {
arweave,
@@ -12,26 +13,19 @@ import {
const contractTxId = 'UC2zwawQoTnh0TNd9mYLQS4wObBBeaOU5LPQTNETqA4';
const localCacheUrl = `https://api.arns.app`;
describe('ANT contract apis', () => {
- const signer = new ArweaveSigner(JSON.parse(process.env.PRIMARY_WALLET_JWK!));
const ant = new ANT({
- signer,
contract: new RemoteContract({
cacheUrl: localCacheUrl,
contractTxId,
+ logger: new DefaultLogger({ level: 'none' }),
}),
});
it('should connect and return a valid instance', async () => {
const jwk = await arweave.wallets.generate();
const signer = new ArweaveSigner(jwk);
- const connectAnt = new ANT({
- contract: new RemoteContract({
- cacheUrl: localCacheUrl,
- contractTxId,
- }),
- });
- expect(connectAnt.connect(signer)).toBeDefined();
- expect(connectAnt).toBeInstanceOf(ANT);
+ expect(ant.connect(signer)).toBeDefined();
+ expect(ant).toBeInstanceOf(ANT);
});
it.each([
@@ -147,10 +141,9 @@ describe('ANT contract apis', () => {
},
);
- it('Should get state with warp contract', async () => {
+ it('should get state with warp contract', async () => {
const jwk = await arweave.wallets.generate();
const signer = new ArweaveSigner(jwk);
- // connecting updates contract to use warp
ant.connect(signer);
const state = await ant.getState();
expect(state).toBeDefined();
diff --git a/tests/integration/ar-io.test.ts b/tests/integration/ar-io.test.ts
index 7e352025..de84088d 100644
--- a/tests/integration/ar-io.test.ts
+++ b/tests/integration/ar-io.test.ts
@@ -2,6 +2,7 @@ import { ArweaveSigner } from 'arbundles';
import { ArIO } from '../../src/common/ar-io.js';
import { RemoteContract } from '../../src/common/contracts/remote-contract.js';
+import { DefaultLogger } from '../../src/common/logger.js';
import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js';
import { ArIOState } from '../../src/contract-state.js';
import {
@@ -20,6 +21,7 @@ describe('ArIO Client', () => {
contract: new RemoteContract({
cacheUrl: localCacheUrl,
contractTxId,
+ logger: new DefaultLogger({ level: 'none' }),
}),
});
diff --git a/tests/integration/arlocal/ant-contract/index.js b/tests/integration/arlocal/ant-contract/index.js
index 32c0ba6b..a9f7bdce 100644
--- a/tests/integration/arlocal/ant-contract/index.js
+++ b/tests/integration/arlocal/ant-contract/index.js
@@ -1136,7 +1136,6 @@ var setTicker = async (state, { caller, input }) => {
const controllers = state.controllers;
const { ticker } = input;
if (!validateSetTicker(input)) {
- console.log(input);
throw new ContractError(INVALID_INPUT_MESSAGE);
}
if (caller !== owner && !controllers.includes(caller)) {
diff --git a/tests/integration/jest.setup.ts b/tests/integration/jest.setup.ts
index 4fbf4ff1..5783e9e1 100644
--- a/tests/integration/jest.setup.ts
+++ b/tests/integration/jest.setup.ts
@@ -22,8 +22,8 @@ async function jestGlobalSetup() {
// deploy example any contract
const [arIOContractDeploy, antContractDeploy] = await Promise.all([
- deployArIOContract({ jwk: wallet, warp }),
- deployANTContract({ jwk: wallet, warp }),
+ deployArIOContract({ jwk: wallet, address, warp }),
+ deployANTContract({ jwk: wallet, address, warp }),
]);
// set in the environment
diff --git a/tests/integration/warp-contract.test.ts b/tests/integration/warp-contract.test.ts
index 58f564d7..1af10598 100644
--- a/tests/integration/warp-contract.test.ts
+++ b/tests/integration/warp-contract.test.ts
@@ -1,8 +1,9 @@
import { ArweaveSigner } from 'arbundles';
-import { Transaction } from 'warp-contracts';
+import Transaction from 'arweave/node/lib/transaction';
import { WarpContract } from '../../src/common/contracts/warp-contract';
import { WriteInteractionError } from '../../src/common/error';
+import { DefaultLogger } from '../../src/common/logger';
import { ANTState } from '../../src/contract-state';
import { arweave, localCacheUrl, warp } from '../constants';
@@ -11,13 +12,14 @@ describe('warp-contract client', () => {
let contractTxId: string;
let contract: WarpContract;
- beforeAll(() => {
+ beforeAll(async () => {
contractTxId = process.env.DEPLOYED_ANT_CONTRACT_TX_ID!;
signer = new ArweaveSigner(JSON.parse(process.env.PRIMARY_WALLET_JWK!));
contract = new WarpContract({
cacheUrl: localCacheUrl,
contractTxId,
warp,
+ logger: new DefaultLogger({ level: 'none' }),
});
});
@@ -38,7 +40,6 @@ describe('warp-contract client', () => {
console.error(e);
return e;
});
-
expect(tx).toBeDefined();
expect(tx).toBeInstanceOf(Transaction);
});
@@ -48,6 +49,7 @@ describe('warp-contract client', () => {
cacheUrl: localCacheUrl,
contractTxId,
arweave,
+ logger: new DefaultLogger({ level: 'none' }),
}).connect(signer);
const error = await contract
diff --git a/tests/utils.ts b/tests/utils.ts
index 53ba6682..054c4a32 100644
--- a/tests/utils.ts
+++ b/tests/utils.ts
@@ -6,9 +6,11 @@ import { ContractDeploy, Warp } from 'warp-contracts';
export async function deployANTContract({
jwk,
+ address,
warp,
}: {
jwk: JWKInterface;
+ address: string;
warp: Warp;
}): Promise {
const src = fs.readFileSync(
@@ -24,24 +26,25 @@ export async function deployANTContract({
'utf8',
),
);
- const owner = await warp.arweave.wallets.jwkToAddress(jwk);
return await warp.deploy({
wallet: jwk,
src: src,
initState: JSON.stringify({
...state,
- owner,
- controllers: [owner],
- balances: { [owner]: 1000000 },
+ owner: address,
+ controllers: [address],
+ balances: { [address]: 1000000 },
}),
});
}
export async function deployArIOContract({
jwk,
+ address,
warp,
}: {
jwk: JWKInterface;
+ address: string;
warp: Warp;
}): Promise {
const src = fs.readFileSync(
@@ -57,14 +60,13 @@ export async function deployArIOContract({
'utf8',
),
);
- const owner = await warp.arweave.wallets.jwkToAddress(jwk);
return await warp.deploy({
wallet: jwk,
src: src,
initState: JSON.stringify({
...state,
- owner,
- balances: { [owner]: 1 * 1_000_000 * 1_000_000 },
+ owner: address,
+ balances: { [address]: 1 * 1_000_000 * 1_000_000 },
}),
});
}
diff --git a/yarn.lock b/yarn.lock
index cdbee451..f02c413c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10215,7 +10215,7 @@ warp-contracts-plugin-deploy@^1.0.13:
node-pre-gyp "^0.17.0"
node-stdlib-browser "^1.2.0"
-warp-contracts@^1.4.38:
+warp-contracts@1.4.39:
version "1.4.39"
resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.39.tgz#b56f4baa61b633c04d8abba65ee5dfca333ffea5"
integrity sha512-Dyk9wjhYceLRSq+U5Ba8h2mTbMPeB1hjdnDhYJP825E7gcc3jGc12GTLynSBqx5eJscOtpGG5Z5SQNxcTOP7/A==