From 4156a0fde1c3266d03581b771279785bb6ae7289 Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Wed, 6 Mar 2024 10:11:01 -0300
Subject: [PATCH 01/14] Move current contractsAPI to deprecated namespace
---
examples/nodejs/src/escrow-flow.ts | 10 +-
.../src/experimental-features/source-map.ts | 2 +-
examples/nodejs/src/marlowe-object-flow.ts | 9 +-
packages/runtime/lifecycle/src/api.ts | 277 +--------
.../src/generic/applicable-actions.ts | 2 +-
.../lifecycle/src/generic/contracts.ts | 253 --------
.../src/generic/deprecated-contracts.ts | 588 ++++++++++++++++++
.../runtime/lifecycle/src/generic/runtime.ts | 16 +-
8 files changed, 621 insertions(+), 536 deletions(-)
delete mode 100644 packages/runtime/lifecycle/src/generic/contracts.ts
create mode 100644 packages/runtime/lifecycle/src/generic/deprecated-contracts.ts
diff --git a/examples/nodejs/src/escrow-flow.ts b/examples/nodejs/src/escrow-flow.ts
index c56e59d2..a8da5c6c 100644
--- a/examples/nodejs/src/escrow-flow.ts
+++ b/examples/nodejs/src/escrow-flow.ts
@@ -88,10 +88,12 @@ async function main(action: "buy" | "sell", otherAddress: string, amount: number
console.log("Mediator: " + Mediator);
console.log("Amount: " + amount);
- const [contractId, txId] = await runtime.contracts.createContract({
- contract: escrow,
- roles: { Buyer, Seller, Mediator },
- });
+ const [contractId, txId] = await runtime.deprecatedContractAPI.createContract(
+ {
+ contract: escrow,
+ roles: { Buyer, Seller, Mediator },
+ }
+ );
console.log("Contract ID: " + contractId);
console.log("Transaction ID: " + txId);
diff --git a/examples/nodejs/src/experimental-features/source-map.ts b/examples/nodejs/src/experimental-features/source-map.ts
index 33ec47bc..2de01a38 100644
--- a/examples/nodejs/src/experimental-features/source-map.ts
+++ b/examples/nodejs/src/experimental-features/source-map.ts
@@ -177,7 +177,7 @@ export async function mkSourceMap(
},
createContract: (options: CreateContractRequestBase) => {
const contract = stripAnnotations(closure.contracts.get(closure.main)!);
- return lifecycle.contracts.createContract({
+ return lifecycle.deprecatedContractAPI.createContract({
...options,
contract,
});
diff --git a/examples/nodejs/src/marlowe-object-flow.ts b/examples/nodejs/src/marlowe-object-flow.ts
index 5ba9378d..965926d4 100644
--- a/examples/nodejs/src/marlowe-object-flow.ts
+++ b/examples/nodejs/src/marlowe-object-flow.ts
@@ -227,8 +227,13 @@ async function contractMenu(
contractId: ContractId
): Promise {
// Get and print the contract logical state.
- const inputHistory = await lifecycle.contracts.getInputHistory(contractId);
- const contractState = getState(datetoTimeout(new Date()), inputHistory, sourceMap);
+ const inputHistory =
+ await lifecycle.deprecatedContractAPI.getInputHistory(contractId);
+ const contractState = getState(
+ datetoTimeout(new Date()),
+ inputHistory,
+ sourceMap
+ );
printState(contractState, scheme);
diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts
index a6097e9c..a875e1a6 100644
--- a/packages/runtime/lifecycle/src/api.ts
+++ b/packages/runtime/lifecycle/src/api.ts
@@ -2,21 +2,11 @@ import { WalletAPI, WalletDI } from "@marlowe.io/wallet/api";
import {
AssetId,
ContractId,
- Metadata,
PayoutAvailable,
PayoutId,
PayoutWithdrawn,
- StakeAddressBech32,
- Tags,
- TxId,
} from "@marlowe.io/runtime-core";
import { RestClient, RestDI } from "@marlowe.io/runtime-rest-client";
-import { RolesConfiguration } from "@marlowe.io/runtime-rest-client/contract";
-import { ISO8601 } from "@marlowe.io/adapter/time";
-import { Contract, Environment, Input, RoleName } from "@marlowe.io/language-core-v1";
-import { Next } from "@marlowe.io/language-core-v1/next";
-import { SingleInputTx } from "@marlowe.io/language-core-v1/transaction.js";
-import { ContractBundleList } from "@marlowe.io/marlowe-object";
import {
ApplicableActionsAPI,
ApplicableAction,
@@ -33,6 +23,10 @@ import {
ClosedContract,
ContractDetails,
} from "./generic/applicable-actions.js";
+import {
+ ContractsAPI,
+ CreateContractRequestBase,
+} from "./generic/deprecated-contracts.js";
export {
ApplicableActionsAPI,
ApplicableAction,
@@ -48,6 +42,7 @@ export {
ActiveContract,
ClosedContract,
ContractDetails,
+ CreateContractRequestBase,
};
/**
@@ -70,271 +65,11 @@ export interface RuntimeLifecycle {
/**
* The contracts API is a high level API that lets you create and interact with Marlowe contracts.
*/
- contracts: ContractsAPI;
+ deprecatedContractAPI: ContractsAPI;
payouts: PayoutsAPI;
applicableActions: ApplicableActionsAPI;
}
-/**
- *
- * @description Dependency Injection for the Contract API
- * @hidden
- */
-export type ContractsDI = WalletDI & RestDI;
-
-/**
- * Request parameters used by {@link api.ContractsAPI#createContract | createContract}.
- * If the contract is "small", you can create it directly with a {@link CreateContractRequestFromContract| core contract},
- * if the contract is "large" you can use a {@link CreateContractRequestFromBundle | contract bundle} instead.
- * Both options share the same {@link CreateContractRequestBase | request parameters}.
- * @category ContractsAPI
- */
-export type CreateContractRequest = CreateContractRequestFromContract | CreateContractRequestFromBundle;
-
-/**
- * @category ContractsAPI
- */
-export interface CreateContractRequestFromContract extends CreateContractRequestBase {
- /**
- * The Marlowe Contract to create
- */
- contract: Contract;
-}
-
-/**
- * @category ContractsAPI
- */
-export interface CreateContractRequestFromBundle extends CreateContractRequestBase {
- /**
- * The Marlowe Object bundle to create
- */
- bundle: ContractBundleList;
-}
-
-/**
- * @category ContractsAPI
- */
-export interface CreateContractRequestBase {
- /**
- * Marlowe contracts can have staking rewards for the ADA locked in the contract.
- * Use this field to set the recipient address of those rewards
- */
- stakeAddress?: StakeAddressBech32;
- /**
- * @experimental
- * The Thread Roles capability is an implementation details of the runtime.
- * It allows you to provide a custom name if the thread role name is conflicting with other role names used.
- * @default
- * - the Thread Role Name is "" by default.
- * @see
- * - https://github.com/input-output-hk/marlowe-cardano/blob/main/marlowe-runtime/doc/open-roles.md
- */
- threadRoleName?: RoleName;
-
- /**
- * Role Token Configuration for the new contract passed in the `contract` field.
- *
- *
Participants
- *
- * Participants ({@link @marlowe.io/language-core-v1!index.Party | Party}) in a Marlowe Contract can be expressed in 2 ways:
- *
- * 1. **By Adressses** : When an address is fixed in the contract we don't need to provide further configuration.
- * 2. **By Roles** : When the participation is done through a Role Token, we need to define if that token is minted as part of the contract creation transaction or if it was minted beforehand.
- *
- *
- *
- *
Configuration Options
- *
- *
- * - **When to create (mint)**
- * - **Within the Runtime** : At the contrat creation, these defined Roles Tokens will be minted "on the fly" by the runtime.
- * - **Without the Runtime** : before the creation, these Role Tokens are already defined (via an NFT platform, `cardano-cli`, another Marlowe Contract Created, etc.. )
- * - **How to distribute**
- * - **Closedly** (Closed Roles) : At the creation of contract or before, the Role Tokens are released to the participants. All the participants are known at the creation and therefore we consider the participation as being closed.
- * - **Openly** (Open Roles) : Whoever applies an input (IDeposit or IChoice) on the contract `contract` first will be identified as a participant by receiving the Role Token in their wallet. In that case, participants are unknown at the creation and the participation is open to any meeting the criteria.
- * - **With or without Metadata**
- * - **Quantities to create(Mint)** : When asking to mint the tokens within the Runtime, quantities can defined as well.
- *
- * Smart Constructors are available to ease these configuration:
- * - {@link @marlowe.io/runtime-rest-client!contract.useMintedRoles}
- * - {@link @marlowe.io/runtime-rest-client!contract.mintRole}
- *
- * @remarks
- * - The Distribution can be a mix of Closed and Open Role Tokens configuration. See examples below.
- *
- *
- * 1. They aren't limited size-wise like regular metadata fields are over Cardano.
- * 2. Metadata can be associated under each tag
- *
- * @example
- * ```ts
- * const myTags : Tags = { "My Tag 1 That can be as long as I want": // Not limited to 64 bytes
- * { a: 0
- * , b : "Tag 1 content" // Limited to 64 bytes (Cardano Metadata constraint)
- * },
- * "MyTag2": { c: 0, d : "Tag 2 content"}};
- * ```
- */
- tags?: Tags;
- /**
- * Cardano Metadata about the contract creation.
- *
Properties
- *
- * Metadata can be expressed as a JSON object with some restrictions:
- * - All top-level keys must be integers between 0 and 2^63 - 1.
- * - Each metadata value is tagged with its type.
- * - Strings must be at most 64 characters long (64 bytes) when UTF-8 is encoded.
- * - Bytestrings are hex-encoded, with a maximum length of 64 bytes.
- *
- * Metadata aren't stored as JSON on the Cardano blockchain but are instead stored using a compact binary encoding (CBOR).
- * The binary encoding of metadata values supports three simple types:
- * - Integers in the range `-(2^63 - 1)` to `2^63 - 1`
- * - Strings (UTF-8 encoded)
- * - Bytestrings
- * - And two compound types:
- * - Lists of metadata values
- * - Mappings from metadata values to metadata values
- *
- * It is possible to transform any JSON object into this schema (See https://developers.cardano.org/docs/transaction-metadata )
- * @see
- * https://developers.cardano.org/docs/transaction-metadata
- */
- metadata?: Metadata;
-
- /**
- * Minimum Lovelace value to add on the UTxO created (Representing the Marlowe Contract Created on the ledger).This value
- * is computed automatically within the Runtime, so this parameter is only necessary if you need some custom adjustment.
- *
- *
Justification
- *
Creating a Marlowe contract over Cardano is about creating UTxO entries on the Ledger.
- *
- * To protect the ledger from growing beyond a certain size that will cost too much to maintain,
- * a constraint called "Minimum ada value requirement (minimumLovelaceUTxODeposit)" that adjust
- * the value (in ADA) of each UTxO has been added.
- *
- * The bigger the UTxOs entries are in terms of bytesize, the higher the value if minimum ADA required.
- *
- * @see
- * https://docs.cardano.org/native-tokens/minimum-ada-value-requirement
- */
- minimumLovelaceUTxODeposit?: number;
-}
-
-/**
- * @category ContractsAPI
- */
-export type ApplyInputsRequest = {
- inputs: Input[];
- tags?: Tags;
- metadata?: Metadata;
- invalidBefore?: ISO8601;
- invalidHereafter?: ISO8601;
-};
-
-/**
- * This Interface provides capabilities for runnning a Contract over Cardano.
- * @category ContractsAPI
- */
-export interface ContractsAPI {
- /**
- * Submit to the Cardano Ledger, the Transaction(Tx) that will create the Marlowe Contract passed in the request.
- * @param createContractRequest Request parameters for creating a Marlowe Contract on Cardano
- * @returns ContractId (Marlowe id) and TxId (Cardano id) of the submitted Tx
- * @throws DecodingError
- */
- createContract(createContractRequest: CreateContractRequest): Promise<[ContractId, TxId]>;
-
- /**
- * Submit to the Cardano Ledger, the Transaction(Tx) that will apply inputs to a given created contract.
- * @param contractId Contract Id where inputs will be applied
- * @param applyInputsRequest inputs to apply
- * @throws DecodingError
- */
- applyInputs(contractId: ContractId, applyInputsRequest: ApplyInputsRequest): Promise;
-
- /**
- * @deprecated Deprecated in favour of {@link @marlowe.io/runtime-lifecycle!api.ApplicableActionsAPI}
- */
- getApplicableInputs(contractId: ContractId, environement: Environment): Promise;
-
- /**
- * @description
- * Fetches all contract ids for contracts on chain that mentions an address in your wallet.
- * @throws Error | DecodingError
- */
- getContractIds(): Promise;
-
- /**
- * Get a list of the applied inputs for a given contract
- * @param contractId
- */
- getInputHistory(contractId: ContractId): Promise;
-}
-
/**
* @hidden
*/
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index 00aeb534..1a34019e 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -1,4 +1,4 @@
-import { ContractsAPI } from "../api.js";
+import { ContractsAPI } from "./deprecated-contracts.js";
import { Monoid } from "fp-ts/lib/Monoid.js";
import * as R from "fp-ts/lib/Record.js";
diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts
deleted file mode 100644
index 852bf3de..00000000
--- a/packages/runtime/lifecycle/src/generic/contracts.ts
+++ /dev/null
@@ -1,253 +0,0 @@
-import * as TE from "fp-ts/lib/TaskEither.js";
-import { pipe } from "fp-ts/lib/function.js";
-import { Option } from "fp-ts/lib/Option.js";
-import { Environment, Party } from "@marlowe.io/language-core-v1";
-import { tryCatchDefault, unsafeTaskEither } from "@marlowe.io/adapter/fp-ts";
-import {
- ApplyInputsRequest,
- ContractsAPI,
- ContractsDI,
- CreateContractRequest,
- CreateContractRequestBase,
-} from "../api.js";
-
-import { getAddressesAndCollaterals, WalletAPI } from "@marlowe.io/wallet/api";
-import {
- PolicyId,
- ContractId,
- contractIdToTxId,
- TxId,
- AddressesAndCollaterals,
- HexTransactionWitnessSet,
- transactionWitnessSetTextEnvelope,
- BlockHeader,
-} from "@marlowe.io/runtime-core";
-
-import { FPTSRestAPI, RestClient, ItemRange } from "@marlowe.io/runtime-rest-client";
-import { DecodingError } from "@marlowe.io/adapter/codec";
-
-import { Next, noNext } from "@marlowe.io/language-core-v1/next";
-import {
- BuildCreateContractTxRequest,
- BuildCreateContractTxRequestOptions,
- TransactionTextEnvelope,
-} from "@marlowe.io/runtime-rest-client/contract";
-import { SingleInputTx } from "@marlowe.io/language-core-v1/transaction.js";
-import { iso8601ToPosixTime } from "@marlowe.io/adapter/time";
-
-export function mkContractLifecycle(
- wallet: WalletAPI,
- deprecatedRestAPI: FPTSRestAPI,
- restClient: RestClient
-): ContractsAPI {
- const di = { wallet, deprecatedRestAPI, restClient };
- return {
- createContract: createContract(di),
- applyInputs: applyInputsTx(di),
- getApplicableInputs: getApplicableInputs(di),
- getContractIds: getContractIds(di),
- getInputHistory: getInputHistory(di),
- };
-}
-const getInputHistory =
- ({ restClient }: ContractsDI) =>
- async (contractId: ContractId): Promise => {
- const transactionHeaders = await restClient.getTransactionsForContract({
- contractId,
- });
- const transactions = await Promise.all(
- transactionHeaders.transactions.map((txHeader) =>
- restClient.getContractTransactionById({
- contractId,
- txId: txHeader.transactionId,
- })
- )
- );
- const sortOptionalBlock = (a: Option, b: Option) => {
- if (a._tag === "None" || b._tag === "None") {
- // TODO: to avoid this error we should provide a higer level function that gets the transactions as the different
- // status and with the appropiate values for each state.
- throw new Error("A confirmed transaction should have a valid block");
- } else {
- if (a.value.blockNo < b.value.blockNo) {
- return -1;
- } else if (a.value.blockNo > b.value.blockNo) {
- return 1;
- } else {
- return 0;
- }
- }
- };
- return transactions
- .filter((tx) => tx.status === "confirmed")
- .sort((a, b) => sortOptionalBlock(a.block, b.block))
- .map((tx) => {
- const interval = {
- from: iso8601ToPosixTime(tx.invalidBefore),
- // FIXME: The runtime method getContractTransactionById responds
- // a cardano timeinterval (which is closed on the lower bound and
- // open in the upper bound), and a Marlowe time interval is closed
- // in both parts. Here I substract 1 millisecond from the open bound
- // to get a "safe" closed bound. But the runtime should instead respond
- // with the same interval as computed here:
- // https://github.com/input-output-hk/marlowe-cardano/blob/9ae464a2be332a004cc4d5284fb1ccaf607fa6c7/marlowe-runtime/tx/Language/Marlowe/Runtime/Transaction/BuildConstraints.hs#L463-L479
- to: iso8601ToPosixTime(tx.invalidHereafter) - 1n,
- };
- if (tx.inputs.length === 0) {
- return [{ interval }];
- } else {
- return tx.inputs.map((input) => ({ input, interval }));
- }
- })
- .flat();
- };
-
-const createContract =
- ({ wallet, restClient }: ContractsDI) =>
- async (createContractRequest: CreateContractRequest): Promise<[ContractId, TxId]> => {
- const addressesAndCollaterals = await getAddressesAndCollaterals(wallet);
-
- const baseRequest: BuildCreateContractTxRequestOptions = {
- version: "v1",
-
- changeAddress: addressesAndCollaterals.changeAddress,
- usedAddresses: addressesAndCollaterals.usedAddresses,
- collateralUTxOs: addressesAndCollaterals.collateralUTxOs,
- stakeAddress: createContractRequest.stakeAddress,
-
- threadRoleName: createContractRequest.threadRoleName,
- roles: createContractRequest.roles,
-
- tags: createContractRequest.tags,
- metadata: createContractRequest.metadata,
- minimumLovelaceUTxODeposit: createContractRequest.minimumLovelaceUTxODeposit,
- };
-
- let restClientRequest: BuildCreateContractTxRequest;
- if ("contract" in createContractRequest) {
- restClientRequest = {
- ...baseRequest,
-
- contract: createContractRequest.contract,
- };
- } else {
- const contractSources = await restClient.createContractSources({
- bundle: createContractRequest.bundle,
- });
- restClientRequest = {
- ...baseRequest,
- sourceId: contractSources.contractSourceId,
- };
- }
- const buildCreateContractTxResponse = await restClient.buildCreateContractTx(restClientRequest);
- const contractId = buildCreateContractTxResponse.contractId;
-
- const hexTransactionWitnessSet = await wallet.signTx(buildCreateContractTxResponse.tx.cborHex);
-
- await restClient.submitContract({
- contractId,
- txEnvelope: transactionWitnessSetTextEnvelope(hexTransactionWitnessSet),
- });
- return [contractId, contractIdToTxId(contractId)];
- };
-
-const applyInputsTx =
- ({ wallet, deprecatedRestAPI }: ContractsDI) =>
- async (contractId: ContractId, applyInputsRequest: ApplyInputsRequest): Promise => {
- return unsafeTaskEither(applyInputsTxFpTs(deprecatedRestAPI)(wallet)(contractId)(applyInputsRequest));
- };
-
-const getApplicableInputs =
- ({ wallet, deprecatedRestAPI }: ContractsDI) =>
- async (contractId: ContractId, environement: Environment): Promise => {
- const contractDetails = await unsafeTaskEither(deprecatedRestAPI.contracts.contract.get(contractId));
- if (!contractDetails.state) {
- return noNext;
- } else {
- const parties = await getParties(wallet)(contractDetails.roleTokenMintingPolicyId);
- return await unsafeTaskEither(deprecatedRestAPI.contracts.contract.next(contractId)(environement)(parties));
- }
- };
-
-const getContractIds =
- ({ deprecatedRestAPI, wallet }: ContractsDI) =>
- async (): Promise => {
- const partyAddresses = [await wallet.getChangeAddress(), ...(await wallet.getUsedAddresses())];
- const kwargs = { tags: [], partyAddresses, partyRoles: [] };
- const loop = async (acc: ContractId[], range?: ItemRange): Promise => {
- const result = await deprecatedRestAPI.contracts.getHeadersByRange(range)(kwargs)();
- if (result._tag === "Left") throw result.left;
- const response = result.right;
- const contractIds = [...acc, ...response.contracts.map(({ contractId }) => contractId)];
- return response.page.next ? loop(contractIds, response.page.next) : contractIds;
- };
- return loop([]);
- };
-
-const getParties: (walletApi: WalletAPI) => (roleTokenMintingPolicyId: PolicyId) => Promise =
- (walletAPI) => async (roleMintingPolicyId) => {
- const changeAddress: Party = await walletAPI
- .getChangeAddress()
- .then((addressBech32) => ({ address: addressBech32 }));
- const usedAddresses: Party[] = await walletAPI.getUsedAddresses().then((addressesBech32) =>
- addressesBech32.map((addressBech32) => ({
- address: addressBech32,
- }))
- );
- const roles: Party[] = (await walletAPI.getTokens())
- .filter((token) => token.assetId.policyId == roleMintingPolicyId)
- .map((token) => ({ role_token: token.assetId.policyId }));
- return roles.concat([changeAddress]).concat(usedAddresses);
- };
-
-export const applyInputsTxFpTs: (
- client: FPTSRestAPI
-) => (
- wallet: WalletAPI
-) => (
- contractId: ContractId
-) => (applyInputsRequest: ApplyInputsRequest) => TE.TaskEither =
- (client) => (wallet) => (contractId) => (applyInputsRequest) =>
- pipe(
- tryCatchDefault(() => getAddressesAndCollaterals(wallet)),
- TE.chain((addressesAndCollaterals: AddressesAndCollaterals) =>
- client.contracts.contract.transactions.post(
- contractId,
- {
- inputs: applyInputsRequest.inputs,
- version: "v1",
- tags: applyInputsRequest.tags ? applyInputsRequest.tags : {},
- metadata: applyInputsRequest.metadata ? applyInputsRequest.metadata : {},
- invalidBefore: applyInputsRequest.invalidBefore,
- invalidHereafter: applyInputsRequest.invalidHereafter,
- },
- addressesAndCollaterals
- )
- ),
- TE.chainW((transactionTextEnvelope: TransactionTextEnvelope) =>
- pipe(
- tryCatchDefault(() => wallet.signTx(transactionTextEnvelope.tx.cborHex)),
- TE.chain((hexTransactionWitnessSet: HexTransactionWitnessSet) =>
- client.contracts.contract.transactions.transaction.put(
- contractId,
- transactionTextEnvelope.transactionId,
- hexTransactionWitnessSet
- )
- ),
- TE.map(() => transactionTextEnvelope.transactionId)
- )
- )
- );
-
-export const applyInputsFpTs: (
- client: FPTSRestAPI
-) => (
- wallet: WalletAPI
-) => (
- contractId: ContractId
-) => (applyInputsRequest: ApplyInputsRequest) => TE.TaskEither =
- (client) => (wallet) => (contractId) => (applyInputsRequest) =>
- pipe(
- applyInputsTxFpTs(client)(wallet)(contractId)(applyInputsRequest),
- TE.chainW((txId) => tryCatchDefault(() => wallet.waitConfirmation(txId).then((_) => txId)))
- );
diff --git a/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts b/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts
new file mode 100644
index 00000000..23d17a28
--- /dev/null
+++ b/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts
@@ -0,0 +1,588 @@
+import * as TE from "fp-ts/lib/TaskEither.js";
+import { pipe } from "fp-ts/lib/function.js";
+import { Option } from "fp-ts/lib/Option.js";
+import {
+ Contract,
+ Environment,
+ Input,
+ Party,
+ RoleName,
+} from "@marlowe.io/language-core-v1";
+import { tryCatchDefault, unsafeTaskEither } from "@marlowe.io/adapter/fp-ts";
+
+import {
+ getAddressesAndCollaterals,
+ WalletAPI,
+ WalletDI,
+} from "@marlowe.io/wallet/api";
+import {
+ PolicyId,
+ ContractId,
+ contractIdToTxId,
+ TxId,
+ AddressesAndCollaterals,
+ HexTransactionWitnessSet,
+ transactionWitnessSetTextEnvelope,
+ BlockHeader,
+ StakeAddressBech32,
+ Metadata,
+ Tags,
+} from "@marlowe.io/runtime-core";
+
+import {
+ FPTSRestAPI,
+ RestClient,
+ RestDI,
+ ItemRange,
+} from "@marlowe.io/runtime-rest-client";
+import { DecodingError } from "@marlowe.io/adapter/codec";
+
+import { Next, noNext } from "@marlowe.io/language-core-v1/next";
+import {
+ BuildCreateContractTxRequest,
+ BuildCreateContractTxRequestOptions,
+ RolesConfiguration,
+ TransactionTextEnvelope,
+} from "@marlowe.io/runtime-rest-client/contract";
+import { SingleInputTx } from "@marlowe.io/language-core-v1/transaction.js";
+import { ISO8601, iso8601ToPosixTime } from "@marlowe.io/adapter/time";
+import { ContractBundleList } from "@marlowe.io/marlowe-object";
+
+/**
+ *
+ * @description Dependency Injection for the Contract API
+ * @hidden
+ */
+export type ContractsDI = WalletDI & RestDI;
+
+/**
+ * Request parameters used by {@link api.ContractsAPI#createContract | createContract}.
+ * If the contract is "small", you can create it directly with a {@link CreateContractRequestFromContract| core contract},
+ * if the contract is "large" you can use a {@link CreateContractRequestFromBundle | contract bundle} instead.
+ * Both options share the same {@link CreateContractRequestBase | request parameters}.
+ * @category ContractsAPI
+ */
+export type CreateContractRequest =
+ | CreateContractRequestFromContract
+ | CreateContractRequestFromBundle;
+
+/**
+ * @category ContractsAPI
+ */
+export interface CreateContractRequestFromContract
+ extends CreateContractRequestBase {
+ /**
+ * The Marlowe Contract to create
+ */
+ contract: Contract;
+}
+
+/**
+ * @category ContractsAPI
+ */
+export interface CreateContractRequestFromBundle
+ extends CreateContractRequestBase {
+ /**
+ * The Marlowe Object bundle to create
+ */
+ bundle: ContractBundleList;
+}
+
+/**
+ * @category ContractsAPI
+ */
+export interface CreateContractRequestBase {
+ /**
+ * Marlowe contracts can have staking rewards for the ADA locked in the contract.
+ * Use this field to set the recipient address of those rewards
+ */
+ stakeAddress?: StakeAddressBech32;
+ /**
+ * @experimental
+ * The Thread Roles capability is an implementation details of the runtime.
+ * It allows you to provide a custom name if the thread role name is conflicting with other role names used.
+ * @default
+ * - the Thread Role Name is "" by default.
+ * @see
+ * - https://github.com/input-output-hk/marlowe-cardano/blob/main/marlowe-runtime/doc/open-roles.md
+ */
+ threadRoleName?: RoleName;
+
+ /**
+ * Role Token Configuration for the new contract passed in the `contract` field.
+ *
+ *
Participants
+ *
+ * Participants ({@link @marlowe.io/language-core-v1!index.Party | Party}) in a Marlowe Contract can be expressed in 2 ways:
+ *
+ * 1. **By Adressses** : When an address is fixed in the contract we don't need to provide further configuration.
+ * 2. **By Roles** : When the participation is done through a Role Token, we need to define if that token is minted as part of the contract creation transaction or if it was minted beforehand.
+ *
+ *
+ *
+ *
Configuration Options
+ *
+ *
+ * - **When to create (mint)**
+ * - **Within the Runtime** : At the contrat creation, these defined Roles Tokens will be minted "on the fly" by the runtime.
+ * - **Without the Runtime** : before the creation, these Role Tokens are already defined (via an NFT platform, `cardano-cli`, another Marlowe Contract Created, etc.. )
+ * - **How to distribute**
+ * - **Closedly** (Closed Roles) : At the creation of contract or before, the Role Tokens are released to the participants. All the participants are known at the creation and therefore we consider the participation as being closed.
+ * - **Openly** (Open Roles) : Whoever applies an input (IDeposit or IChoice) on the contract `contract` first will be identified as a participant by receiving the Role Token in their wallet. In that case, participants are unknown at the creation and the participation is open to any meeting the criteria.
+ * - **With or without Metadata**
+ * - **Quantities to create(Mint)** : When asking to mint the tokens within the Runtime, quantities can defined as well.
+ *
+ * Smart Constructors are available to ease these configuration:
+ * - {@link @marlowe.io/runtime-rest-client!contract.useMintedRoles}
+ * - {@link @marlowe.io/runtime-rest-client!contract.mintRole}
+ *
+ * @remarks
+ * - The Distribution can be a mix of Closed and Open Role Tokens configuration. See examples below.
+ *
+ *
+ * 1. They aren't limited size-wise like regular metadata fields are over Cardano.
+ * 2. Metadata can be associated under each tag
+ *
+ * @example
+ * ```ts
+ * const myTags : Tags = { "My Tag 1 That can be as long as I want": // Not limited to 64 bytes
+ * { a: 0
+ * , b : "Tag 1 content" // Limited to 64 bytes (Cardano Metadata constraint)
+ * },
+ * "MyTag2": { c: 0, d : "Tag 2 content"}};
+ * ```
+ */
+ tags?: Tags;
+ /**
+ * Cardano Metadata about the contract creation.
+ *
Properties
+ *
+ * Metadata can be expressed as a JSON object with some restrictions:
+ * - All top-level keys must be integers between 0 and 2^63 - 1.
+ * - Each metadata value is tagged with its type.
+ * - Strings must be at most 64 characters long (64 bytes) when UTF-8 is encoded.
+ * - Bytestrings are hex-encoded, with a maximum length of 64 bytes.
+ *
+ * Metadata aren't stored as JSON on the Cardano blockchain but are instead stored using a compact binary encoding (CBOR).
+ * The binary encoding of metadata values supports three simple types:
+ * - Integers in the range `-(2^63 - 1)` to `2^63 - 1`
+ * - Strings (UTF-8 encoded)
+ * - Bytestrings
+ * - And two compound types:
+ * - Lists of metadata values
+ * - Mappings from metadata values to metadata values
+ *
+ * It is possible to transform any JSON object into this schema (See https://developers.cardano.org/docs/transaction-metadata )
+ * @see
+ * https://developers.cardano.org/docs/transaction-metadata
+ */
+ metadata?: Metadata;
+
+ /**
+ * Minimum Lovelace value to add on the UTxO created (Representing the Marlowe Contract Created on the ledger).This value
+ * is computed automatically within the Runtime, so this parameter is only necessary if you need some custom adjustment.
+ *
+ *
Justification
+ *
Creating a Marlowe contract over Cardano is about creating UTxO entries on the Ledger.
+ *
+ * To protect the ledger from growing beyond a certain size that will cost too much to maintain,
+ * a constraint called "Minimum ada value requirement (minimumLovelaceUTxODeposit)" that adjust
+ * the value (in ADA) of each UTxO has been added.
+ *
+ * The bigger the UTxOs entries are in terms of bytesize, the higher the value if minimum ADA required.
+ *
+ * @see
+ * https://docs.cardano.org/native-tokens/minimum-ada-value-requirement
+ */
+ minimumLovelaceUTxODeposit?: number;
+}
+
+/**
+ * @category ContractsAPI
+ */
+export type ApplyInputsRequest = {
+ inputs: Input[];
+ tags?: Tags;
+ metadata?: Metadata;
+ invalidBefore?: ISO8601;
+ invalidHereafter?: ISO8601;
+};
+
+/**
+ * This Interface provides capabilities for runnning a Contract over Cardano.
+ * @category ContractsAPI
+ */
+export interface ContractsAPI {
+ /**
+ * Submit to the Cardano Ledger, the Transaction(Tx) that will create the Marlowe Contract passed in the request.
+ * @param createContractRequest Request parameters for creating a Marlowe Contract on Cardano
+ * @returns ContractId (Marlowe id) and TxId (Cardano id) of the submitted Tx
+ * @throws DecodingError
+ */
+ createContract(
+ createContractRequest: CreateContractRequest
+ ): Promise<[ContractId, TxId]>;
+
+ /**
+ * Submit to the Cardano Ledger, the Transaction(Tx) that will apply inputs to a given created contract.
+ * @param contractId Contract Id where inputs will be applied
+ * @param applyInputsRequest inputs to apply
+ * @throws DecodingError
+ */
+ applyInputs(
+ contractId: ContractId,
+ applyInputsRequest: ApplyInputsRequest
+ ): Promise;
+
+ /**
+ * @deprecated Deprecated in favour of {@link @marlowe.io/runtime-lifecycle!api.ApplicableActionsAPI}
+ */
+ getApplicableInputs(
+ contractId: ContractId,
+ environement: Environment
+ ): Promise;
+
+ /**
+ * @description
+ * Fetches all contract ids for contracts on chain that mentions an address in your wallet.
+ * @throws Error | DecodingError
+ */
+ getContractIds(): Promise;
+
+ /**
+ * Get a list of the applied inputs for a given contract
+ * @param contractId
+ */
+ getInputHistory(contractId: ContractId): Promise;
+}
+
+export function mkContractLifecycle(
+ wallet: WalletAPI,
+ deprecatedRestAPI: FPTSRestAPI,
+ restClient: RestClient
+): ContractsAPI {
+ const di = { wallet, deprecatedRestAPI, restClient };
+ return {
+ createContract: createContract(di),
+ applyInputs: applyInputsTx(di),
+ getApplicableInputs: getApplicableInputs(di),
+ getContractIds: getContractIds(di),
+ getInputHistory: getInputHistory(di),
+ };
+}
+const getInputHistory =
+ ({ restClient }: ContractsDI) =>
+ async (contractId: ContractId): Promise => {
+ const transactionHeaders = await restClient.getTransactionsForContract({
+ contractId,
+ });
+ const transactions = await Promise.all(
+ transactionHeaders.transactions.map((txHeader) =>
+ restClient.getContractTransactionById({
+ contractId,
+ txId: txHeader.transactionId,
+ })
+ )
+ );
+ const sortOptionalBlock = (
+ a: Option,
+ b: Option
+ ) => {
+ if (a._tag === "None" || b._tag === "None") {
+ // TODO: to avoid this error we should provide a higer level function that gets the transactions as the different
+ // status and with the appropiate values for each state.
+ throw new Error("A confirmed transaction should have a valid block");
+ } else {
+ if (a.value.blockNo < b.value.blockNo) {
+ return -1;
+ } else if (a.value.blockNo > b.value.blockNo) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ };
+ return transactions
+ .filter((tx) => tx.status === "confirmed")
+ .sort((a, b) => sortOptionalBlock(a.block, b.block))
+ .map((tx) => {
+ const interval = {
+ from: iso8601ToPosixTime(tx.invalidBefore),
+ // FIXME: The runtime method getContractTransactionById responds
+ // a cardano timeinterval (which is closed on the lower bound and
+ // open in the upper bound), and a Marlowe time interval is closed
+ // in both parts. Here I substract 1 millisecond from the open bound
+ // to get a "safe" closed bound. But the runtime should instead respond
+ // with the same interval as computed here:
+ // https://github.com/input-output-hk/marlowe-cardano/blob/9ae464a2be332a004cc4d5284fb1ccaf607fa6c7/marlowe-runtime/tx/Language/Marlowe/Runtime/Transaction/BuildConstraints.hs#L463-L479
+ to: iso8601ToPosixTime(tx.invalidHereafter) - 1n,
+ };
+ if (tx.inputs.length === 0) {
+ return [{ interval }];
+ } else {
+ return tx.inputs.map((input) => ({ input, interval }));
+ }
+ })
+ .flat();
+ };
+
+const createContract =
+ ({ wallet, restClient }: ContractsDI) =>
+ async (
+ createContractRequest: CreateContractRequest
+ ): Promise<[ContractId, TxId]> => {
+ const addressesAndCollaterals = await getAddressesAndCollaterals(wallet);
+
+ const baseRequest: BuildCreateContractTxRequestOptions = {
+ version: "v1",
+
+ changeAddress: addressesAndCollaterals.changeAddress,
+ usedAddresses: addressesAndCollaterals.usedAddresses,
+ collateralUTxOs: addressesAndCollaterals.collateralUTxOs,
+ stakeAddress: createContractRequest.stakeAddress,
+
+ threadRoleName: createContractRequest.threadRoleName,
+ roles: createContractRequest.roles,
+
+ tags: createContractRequest.tags,
+ metadata: createContractRequest.metadata,
+ minimumLovelaceUTxODeposit:
+ createContractRequest.minimumLovelaceUTxODeposit,
+ };
+
+ let restClientRequest: BuildCreateContractTxRequest;
+ if ("contract" in createContractRequest) {
+ restClientRequest = {
+ ...baseRequest,
+
+ contract: createContractRequest.contract,
+ };
+ } else {
+ const contractSources = await restClient.createContractSources({
+ bundle: createContractRequest.bundle,
+ });
+ restClientRequest = {
+ ...baseRequest,
+ sourceId: contractSources.contractSourceId,
+ };
+ }
+ const buildCreateContractTxResponse =
+ await restClient.buildCreateContractTx(restClientRequest);
+ const contractId = buildCreateContractTxResponse.contractId;
+
+ const hexTransactionWitnessSet = await wallet.signTx(
+ buildCreateContractTxResponse.tx.cborHex
+ );
+
+ await restClient.submitContract({
+ contractId,
+ txEnvelope: transactionWitnessSetTextEnvelope(hexTransactionWitnessSet),
+ });
+ return [contractId, contractIdToTxId(contractId)];
+ };
+
+const applyInputsTx =
+ ({ wallet, deprecatedRestAPI }: ContractsDI) =>
+ async (
+ contractId: ContractId,
+ applyInputsRequest: ApplyInputsRequest
+ ): Promise => {
+ return unsafeTaskEither(
+ applyInputsTxFpTs(deprecatedRestAPI)(wallet)(contractId)(
+ applyInputsRequest
+ )
+ );
+ };
+
+const getApplicableInputs =
+ ({ wallet, deprecatedRestAPI }: ContractsDI) =>
+ async (contractId: ContractId, environement: Environment): Promise => {
+ const contractDetails = await unsafeTaskEither(
+ deprecatedRestAPI.contracts.contract.get(contractId)
+ );
+ if (!contractDetails.state) {
+ return noNext;
+ } else {
+ const parties = await getParties(wallet)(
+ contractDetails.roleTokenMintingPolicyId
+ );
+ return await unsafeTaskEither(
+ deprecatedRestAPI.contracts.contract.next(contractId)(environement)(
+ parties
+ )
+ );
+ }
+ };
+
+const getContractIds =
+ ({ deprecatedRestAPI, wallet }: ContractsDI) =>
+ async (): Promise => {
+ const partyAddresses = [
+ await wallet.getChangeAddress(),
+ ...(await wallet.getUsedAddresses()),
+ ];
+ const kwargs = { tags: [], partyAddresses, partyRoles: [] };
+ const loop = async (
+ acc: ContractId[],
+ range?: ItemRange
+ ): Promise => {
+ const result =
+ await deprecatedRestAPI.contracts.getHeadersByRange(range)(kwargs)();
+ if (result._tag === "Left") throw result.left;
+ const response = result.right;
+ const contractIds = [
+ ...acc,
+ ...response.contracts.map(({ contractId }) => contractId),
+ ];
+ return response.page.next
+ ? loop(contractIds, response.page.next)
+ : contractIds;
+ };
+ return loop([]);
+ };
+
+const getParties: (
+ walletApi: WalletAPI
+) => (roleTokenMintingPolicyId: PolicyId) => Promise =
+ (walletAPI) => async (roleMintingPolicyId) => {
+ const changeAddress: Party = await walletAPI
+ .getChangeAddress()
+ .then((addressBech32) => ({ address: addressBech32 }));
+ const usedAddresses: Party[] = await walletAPI
+ .getUsedAddresses()
+ .then((addressesBech32) =>
+ addressesBech32.map((addressBech32) => ({
+ address: addressBech32,
+ }))
+ );
+ const roles: Party[] = (await walletAPI.getTokens())
+ .filter((token) => token.assetId.policyId == roleMintingPolicyId)
+ .map((token) => ({ role_token: token.assetId.policyId }));
+ return roles.concat([changeAddress]).concat(usedAddresses);
+ };
+
+export const applyInputsTxFpTs: (
+ client: FPTSRestAPI
+) => (
+ wallet: WalletAPI
+) => (
+ contractId: ContractId
+) => (
+ applyInputsRequest: ApplyInputsRequest
+) => TE.TaskEither =
+ (client) => (wallet) => (contractId) => (applyInputsRequest) =>
+ pipe(
+ tryCatchDefault(() => getAddressesAndCollaterals(wallet)),
+ TE.chain((addressesAndCollaterals: AddressesAndCollaterals) =>
+ client.contracts.contract.transactions.post(
+ contractId,
+ {
+ inputs: applyInputsRequest.inputs,
+ version: "v1",
+ tags: applyInputsRequest.tags ? applyInputsRequest.tags : {},
+ metadata: applyInputsRequest.metadata
+ ? applyInputsRequest.metadata
+ : {},
+ invalidBefore: applyInputsRequest.invalidBefore,
+ invalidHereafter: applyInputsRequest.invalidHereafter,
+ },
+ addressesAndCollaterals
+ )
+ ),
+ TE.chainW((transactionTextEnvelope: TransactionTextEnvelope) =>
+ pipe(
+ tryCatchDefault(() =>
+ wallet.signTx(transactionTextEnvelope.tx.cborHex)
+ ),
+ TE.chain((hexTransactionWitnessSet: HexTransactionWitnessSet) =>
+ client.contracts.contract.transactions.transaction.put(
+ contractId,
+ transactionTextEnvelope.transactionId,
+ hexTransactionWitnessSet
+ )
+ ),
+ TE.map(() => transactionTextEnvelope.transactionId)
+ )
+ )
+ );
+
+export const applyInputsFpTs: (
+ client: FPTSRestAPI
+) => (
+ wallet: WalletAPI
+) => (
+ contractId: ContractId
+) => (
+ applyInputsRequest: ApplyInputsRequest
+) => TE.TaskEither =
+ (client) => (wallet) => (contractId) => (applyInputsRequest) =>
+ pipe(
+ applyInputsTxFpTs(client)(wallet)(contractId)(applyInputsRequest),
+ TE.chainW((txId) =>
+ tryCatchDefault(() => wallet.waitConfirmation(txId).then((_) => txId))
+ )
+ );
diff --git a/packages/runtime/lifecycle/src/generic/runtime.ts b/packages/runtime/lifecycle/src/generic/runtime.ts
index 38562fd2..c8030b8c 100644
--- a/packages/runtime/lifecycle/src/generic/runtime.ts
+++ b/packages/runtime/lifecycle/src/generic/runtime.ts
@@ -4,7 +4,7 @@ import { WalletAPI } from "@marlowe.io/wallet/api";
import { FPTSRestAPI, RestClient } from "@marlowe.io/runtime-rest-client";
import { mkPayoutLifecycle } from "./payouts.js";
-import { mkContractLifecycle } from "./contracts.js";
+import { mkContractLifecycle } from "./deprecated-contracts.js";
import { mkApplicableActionsAPI } from "./applicable-actions.js";
export function mkRuntimeLifecycle(
@@ -12,12 +12,20 @@ export function mkRuntimeLifecycle(
restClient: RestClient,
wallet: WalletAPI
): RuntimeLifecycle {
- const contracts = mkContractLifecycle(wallet, deprecatedRestAPI, restClient);
+ const deprecatedContractAPI = mkContractLifecycle(
+ wallet,
+ deprecatedRestAPI,
+ restClient
+ );
return {
wallet: wallet,
restClient,
- contracts,
+ deprecatedContractAPI,
payouts: mkPayoutLifecycle(wallet, deprecatedRestAPI, restClient),
- applicableActions: mkApplicableActionsAPI(restClient, wallet, contracts),
+ applicableActions: mkApplicableActionsAPI(
+ restClient,
+ wallet,
+ deprecatedContractAPI
+ ),
};
}
From 563399395a582771c48dc92676fbbb6d049b1e44 Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Wed, 6 Mar 2024 12:27:13 -0300
Subject: [PATCH 02/14] start of the implementation of the new contract api
---
packages/runtime/client/rest/src/index.ts | 4 +-
packages/runtime/lifecycle/src/api.ts | 15 +-
.../src/generic/applicable-actions.ts | 39 +----
.../src/generic/deprecated-contracts.ts | 112 ++++----------
.../lifecycle/src/generic/new-contract-api.ts | 143 ++++++++++++++++++
.../runtime/lifecycle/src/generic/runtime.ts | 2 +
6 files changed, 198 insertions(+), 117 deletions(-)
create mode 100644 packages/runtime/lifecycle/src/generic/new-contract-api.ts
diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts
index 01cd697a..ff976f87 100644
--- a/packages/runtime/client/rest/src/index.ts
+++ b/packages/runtime/client/rest/src/index.ts
@@ -637,7 +637,9 @@ export interface ContractsAPI {
* @description Dependency Injection for the Rest Client API
* @hidden
*/
-export type RestDI = { deprecatedRestAPI: FPTSRestAPI; restClient: RestClient };
+export type RestDI = { restClient: RestClient };
+
+export type DeprecatedRestDI = { deprecatedRestAPI: FPTSRestAPI };
/**
* @hidden
diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts
index a875e1a6..c4c22239 100644
--- a/packages/runtime/lifecycle/src/api.ts
+++ b/packages/runtime/lifecycle/src/api.ts
@@ -6,7 +6,12 @@ import {
PayoutId,
PayoutWithdrawn,
} from "@marlowe.io/runtime-core";
-import { RestClient, RestDI } from "@marlowe.io/runtime-rest-client";
+import {
+ DeprecatedRestDI,
+ RestClient,
+ RestDI,
+} from "@marlowe.io/runtime-rest-client";
+
import {
ApplicableActionsAPI,
ApplicableAction,
@@ -19,10 +24,12 @@ import {
CanDeposit,
CanNotify,
GetApplicableActionsResponse,
+} from "./generic/applicable-actions.js";
+import {
ActiveContract,
ClosedContract,
ContractDetails,
-} from "./generic/applicable-actions.js";
+} from "./generic/new-contract-api.js";
import {
ContractsAPI,
CreateContractRequestBase,
@@ -44,6 +51,7 @@ export {
ContractDetails,
CreateContractRequestBase,
};
+import * as NewContract from "./generic/new-contract-api.js";
/**
* This is the main entry point of the @marlowe.io/runtime-lifecycle package. It provides a set of APIs to
@@ -62,6 +70,7 @@ export interface RuntimeLifecycle {
* Access to the low-level REST API as defined in the {@link @marlowe.io/runtime-rest-client! } package. It is re-exported here for convenience.
*/
restClient: RestClient;
+ newContractAPI: NewContract.ContractsAPI;
/**
* The contracts API is a high level API that lets you create and interact with Marlowe contracts.
*/
@@ -73,7 +82,7 @@ export interface RuntimeLifecycle {
/**
* @hidden
*/
-export type PayoutsDI = WalletDI & RestDI;
+export type PayoutsDI = WalletDI & RestDI & DeprecatedRestDI;
/**
* @category PayoutsAPI
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index 1a34019e..4b9ffbc3 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -38,6 +38,11 @@ import { WalletAPI } from "@marlowe.io/wallet";
import * as Big from "@marlowe.io/adapter/bigint";
import { ContractSourceId } from "@marlowe.io/marlowe-object";
import { posixTimeToIso8601 } from "@marlowe.io/adapter/time";
+import {
+ ActiveContract,
+ ContractDetails,
+ GetContractDetailsDI,
+} from "./new-contract-api.js";
/**
* @experimental
@@ -755,37 +760,3 @@ function getApplicableActionFromCase(env: Environment, state: MarloweState, aCas
});
}
}
-
-// #region High level Contract Details
-/**
- * @category New ContractsAPI
- */
-export type ClosedContract = {
- type: "closed";
-};
-
-/**
- * @category New ContractsAPI
- */
-export type ActiveContract = {
- type: "active";
- contractId: ContractId;
- currentState: MarloweState;
- currentContract: Contract;
- roleTokenMintingPolicyId: PolicyId;
-};
-
-/**
- * This is the start of a high level API to get the contract details.
- * The current restAPI is not clear wether the details that you get are
- * from a closed or active contract. This API is just the start to get
- * getApplicableInputs ready in production, but as part of a ContractsAPI
- * refactoring, the whole contract details should be modeled.
- * @category New ContractsAPI
- */
-export type ContractDetails = ClosedContract | ActiveContract;
-
-type GetContractDetailsDI = {
- getContractDetails: (contractId: ContractId) => Promise;
-};
-// #endregion
diff --git a/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts b/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts
index 23d17a28..c4f55dcf 100644
--- a/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts
+++ b/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts
@@ -34,6 +34,7 @@ import {
RestClient,
RestDI,
ItemRange,
+ DeprecatedRestDI,
} from "@marlowe.io/runtime-rest-client";
import { DecodingError } from "@marlowe.io/adapter/codec";
@@ -328,13 +329,13 @@ export function mkContractLifecycle(
const di = { wallet, deprecatedRestAPI, restClient };
return {
createContract: createContract(di),
- applyInputs: applyInputsTx(di),
+ applyInputs: applyInputs(di),
getApplicableInputs: getApplicableInputs(di),
getContractIds: getContractIds(di),
getInputHistory: getInputHistory(di),
};
}
-const getInputHistory =
+export const getInputHistory =
({ restClient }: ContractsDI) =>
async (contractId: ContractId): Promise => {
const transactionHeaders = await restClient.getTransactionsForContract({
@@ -390,7 +391,7 @@ const getInputHistory =
.flat();
};
-const createContract =
+export const createContract =
({ wallet, restClient }: ContractsDI) =>
async (
createContractRequest: CreateContractRequest
@@ -445,21 +446,8 @@ const createContract =
return [contractId, contractIdToTxId(contractId)];
};
-const applyInputsTx =
- ({ wallet, deprecatedRestAPI }: ContractsDI) =>
- async (
- contractId: ContractId,
- applyInputsRequest: ApplyInputsRequest
- ): Promise => {
- return unsafeTaskEither(
- applyInputsTxFpTs(deprecatedRestAPI)(wallet)(contractId)(
- applyInputsRequest
- )
- );
- };
-
const getApplicableInputs =
- ({ wallet, deprecatedRestAPI }: ContractsDI) =>
+ ({ wallet, deprecatedRestAPI }: ContractsDI & DeprecatedRestDI) =>
async (contractId: ContractId, environement: Environment): Promise => {
const contractDetails = await unsafeTaskEither(
deprecatedRestAPI.contracts.contract.get(contractId)
@@ -479,7 +467,7 @@ const getApplicableInputs =
};
const getContractIds =
- ({ deprecatedRestAPI, wallet }: ContractsDI) =>
+ ({ deprecatedRestAPI, wallet }: ContractsDI & DeprecatedRestDI) =>
async (): Promise => {
const partyAddresses = [
await wallet.getChangeAddress(),
@@ -525,64 +513,30 @@ const getParties: (
return roles.concat([changeAddress]).concat(usedAddresses);
};
-export const applyInputsTxFpTs: (
- client: FPTSRestAPI
-) => (
- wallet: WalletAPI
-) => (
- contractId: ContractId
-) => (
- applyInputsRequest: ApplyInputsRequest
-) => TE.TaskEither =
- (client) => (wallet) => (contractId) => (applyInputsRequest) =>
- pipe(
- tryCatchDefault(() => getAddressesAndCollaterals(wallet)),
- TE.chain((addressesAndCollaterals: AddressesAndCollaterals) =>
- client.contracts.contract.transactions.post(
- contractId,
- {
- inputs: applyInputsRequest.inputs,
- version: "v1",
- tags: applyInputsRequest.tags ? applyInputsRequest.tags : {},
- metadata: applyInputsRequest.metadata
- ? applyInputsRequest.metadata
- : {},
- invalidBefore: applyInputsRequest.invalidBefore,
- invalidHereafter: applyInputsRequest.invalidHereafter,
- },
- addressesAndCollaterals
- )
- ),
- TE.chainW((transactionTextEnvelope: TransactionTextEnvelope) =>
- pipe(
- tryCatchDefault(() =>
- wallet.signTx(transactionTextEnvelope.tx.cborHex)
- ),
- TE.chain((hexTransactionWitnessSet: HexTransactionWitnessSet) =>
- client.contracts.contract.transactions.transaction.put(
- contractId,
- transactionTextEnvelope.transactionId,
- hexTransactionWitnessSet
- )
- ),
- TE.map(() => transactionTextEnvelope.transactionId)
- )
- )
- );
-
-export const applyInputsFpTs: (
- client: FPTSRestAPI
-) => (
- wallet: WalletAPI
-) => (
- contractId: ContractId
-) => (
- applyInputsRequest: ApplyInputsRequest
-) => TE.TaskEither =
- (client) => (wallet) => (contractId) => (applyInputsRequest) =>
- pipe(
- applyInputsTxFpTs(client)(wallet)(contractId)(applyInputsRequest),
- TE.chainW((txId) =>
- tryCatchDefault(() => wallet.waitConfirmation(txId).then((_) => txId))
- )
- );
+export const applyInputs =
+ ({ wallet, restClient }: ContractsDI) =>
+ async (
+ contractId: ContractId,
+ applyInputsRequest: ApplyInputsRequest
+ ): Promise => {
+ const addressesAndCollaterals = await getAddressesAndCollaterals(wallet);
+ const envelope = await restClient.applyInputsToContract({
+ contractId,
+ changeAddress: addressesAndCollaterals.changeAddress,
+ usedAddresses: addressesAndCollaterals.usedAddresses,
+ collateralUTxOs: addressesAndCollaterals.collateralUTxOs,
+ inputs: applyInputsRequest.inputs,
+ invalidBefore: applyInputsRequest.invalidBefore,
+ invalidHereafter: applyInputsRequest.invalidHereafter,
+ version: "v1",
+ metadata: applyInputsRequest.metadata,
+ tags: applyInputsRequest.tags,
+ });
+ const signed = await wallet.signTx(envelope.tx.cborHex);
+ await restClient.submitContractTransaction({
+ contractId,
+ transactionId: envelope.transactionId,
+ hexTransactionWitnessSet: signed,
+ });
+ return envelope.transactionId;
+ };
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
new file mode 100644
index 00000000..82a9f3f2
--- /dev/null
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -0,0 +1,143 @@
+import {
+ ContractId,
+ PolicyId,
+ TxId,
+ contractIdToTxId,
+} from "@marlowe.io/runtime-core";
+import {
+ ApplyInputsRequest,
+ CreateContractRequest,
+ createContract,
+ applyInputs,
+ getInputHistory,
+} from "./deprecated-contracts.js";
+import { SingleInputTx } from "@marlowe.io/language-core-v1/semantics";
+import { Contract, MarloweState } from "@marlowe.io/language-core-v1";
+import { RestDI } from "@marlowe.io/runtime-rest-client";
+import { WalletDI } from "@marlowe.io/wallet";
+
+/**
+ *
+ * @description Dependency Injection for the Contract API
+ * @hidden
+ */
+export type ContractsDI = WalletDI & RestDI;
+
+/**
+ * TODO comment
+ * @category New ContractsAPI
+ */
+export interface ContractsAPI {
+ createContract(
+ createContractRequest: CreateContractRequest
+ ): Promise;
+ loadContract(contractId: ContractId): Promise;
+}
+
+export function mkContractsAPI(di: ContractsDI): ContractsAPI {
+ return {
+ createContract: async (request) => {
+ const [contractId, _] = await createContract(di)(request);
+ return mkContractInstanceAPI(di, contractId);
+ },
+ loadContract: async (contractId) => {
+ return mkContractInstanceAPI(di, contractId);
+ },
+ };
+}
+
+/**
+ * TODO comment
+ * @category New ContractsAPI
+ */
+export interface ContractInstanceAPI {
+ contractId: ContractId;
+ waitForConfirmation: () => Promise;
+ getContractDetails: () => Promise;
+ applyInputs(applyInputsRequest: ApplyInputsRequest): Promise;
+ // TODO: ApplicableInputs
+ /**
+ * Get a list of the applied inputs for the contract
+ */
+ getInputHistory(): Promise;
+}
+
+function mkContractInstanceAPI(
+ di: ContractsDI,
+ contractId: ContractId
+): ContractInstanceAPI {
+ const contractCreationTxId = contractIdToTxId(contractId);
+ return {
+ contractId,
+ waitForConfirmation: async () => {
+ return di.wallet.waitConfirmation(contractCreationTxId);
+ },
+ getContractDetails: async () => {
+ return getContractDetails(di, contractId);
+ },
+ applyInputs: async (request) => {
+ return applyInputs(di)(contractId, request);
+ },
+ getInputHistory: async () => {
+ // TODO: We can optimize this by only asking for the new transaction headers
+ // and only asking for contract details of the new transactions.
+ return getInputHistory(di)(contractId);
+ },
+ };
+}
+
+async function getContractDetails(
+ di: ContractsDI,
+ contractId: ContractId
+): Promise {
+ const contractDetails = await di.restClient.getContractById({ contractId });
+ if (
+ typeof contractDetails.state === "undefined" ||
+ typeof contractDetails.currentContract === "undefined"
+ ) {
+ return { type: "closed" };
+ } else {
+ return {
+ type: "active",
+ contractId,
+ currentState: contractDetails.state,
+ currentContract: contractDetails.currentContract,
+ roleTokenMintingPolicyId: contractDetails.roleTokenMintingPolicyId,
+ };
+ }
+}
+
+/**
+ * TODO comment
+ * @category New ContractsAPI
+ */
+export type ClosedContract = {
+ type: "closed";
+};
+
+/**
+ * TODO comment
+ * @category New ContractsAPI
+ */
+export type ActiveContract = {
+ type: "active";
+ contractId: ContractId;
+ currentState: MarloweState;
+ currentContract: Contract;
+ roleTokenMintingPolicyId: PolicyId;
+};
+
+/**
+ * TODO comment
+ * TODO: Fill with all the information we want to expose
+ * @category New ContractsAPI
+ */
+export type ContractDetails = ClosedContract | ActiveContract;
+
+/**
+ * TODO comment
+ * @category New ContractsAPI
+ */
+export type GetContractDetailsDI = {
+ getContractDetails: (contractId: ContractId) => Promise;
+};
diff --git a/packages/runtime/lifecycle/src/generic/runtime.ts b/packages/runtime/lifecycle/src/generic/runtime.ts
index c8030b8c..9cb80402 100644
--- a/packages/runtime/lifecycle/src/generic/runtime.ts
+++ b/packages/runtime/lifecycle/src/generic/runtime.ts
@@ -6,6 +6,7 @@ import { FPTSRestAPI, RestClient } from "@marlowe.io/runtime-rest-client";
import { mkPayoutLifecycle } from "./payouts.js";
import { mkContractLifecycle } from "./deprecated-contracts.js";
import { mkApplicableActionsAPI } from "./applicable-actions.js";
+import * as NewContract from "./new-contract-api.js";
export function mkRuntimeLifecycle(
deprecatedRestAPI: FPTSRestAPI,
@@ -21,6 +22,7 @@ export function mkRuntimeLifecycle(
wallet: wallet,
restClient,
deprecatedContractAPI,
+ newContractAPI: NewContract.mkContractsAPI({ wallet, restClient }),
payouts: mkPayoutLifecycle(wallet, deprecatedRestAPI, restClient),
applicableActions: mkApplicableActionsAPI(
restClient,
From 959ed22b6405c96e9d9e79bcf26a5d560819ba75 Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Thu, 7 Mar 2024 17:25:45 -0300
Subject: [PATCH 03/14] Initial version of applicable action in the new
contract api
---
.../src/generic/applicable-actions.ts | 95 +++++------
.../lifecycle/src/generic/new-contract-api.ts | 156 ++++++++++++++----
.../runtime/lifecycle/src/generic/runtime.ts | 2 +-
3 files changed, 169 insertions(+), 84 deletions(-)
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index 4b9ffbc3..2e94a6ff 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -1,4 +1,8 @@
-import { ContractsAPI } from "./deprecated-contracts.js";
+import {
+ ApplyInputsRequest,
+ ContractsAPI,
+ applyInputs,
+} from "./deprecated-contracts.js";
import { Monoid } from "fp-ts/lib/Monoid.js";
import * as R from "fp-ts/lib/Record.js";
@@ -32,17 +36,20 @@ import {
reduceContractUntilQuiescent,
TransactionSuccess,
} from "@marlowe.io/language-core-v1/semantics";
-import { AddressBech32, ContractId, Metadata, PolicyId, Tags, TxId } from "@marlowe.io/runtime-core";
-import { RestClient, Tip } from "@marlowe.io/runtime-rest-client";
-import { WalletAPI } from "@marlowe.io/wallet";
+import {
+ AddressBech32,
+ ContractId,
+ Metadata,
+ PolicyId,
+ Tags,
+ TxId,
+} from "@marlowe.io/runtime-core";
+import { RestClient, RestDI, Tip } from "@marlowe.io/runtime-rest-client";
+import { WalletAPI, WalletDI } from "@marlowe.io/wallet";
import * as Big from "@marlowe.io/adapter/bigint";
import { ContractSourceId } from "@marlowe.io/marlowe-object";
import { posixTimeToIso8601 } from "@marlowe.io/adapter/time";
-import {
- ActiveContract,
- ContractDetails,
- GetContractDetailsDI,
-} from "./new-contract-api.js";
+import { ActiveContract, ContractDetails } from "./new-contract-api.js";
/**
* @experimental
@@ -62,7 +69,10 @@ export interface ApplicableActionsAPI {
* function should receive the contractDetails and just return the actions.
* To do this, we should refactor the {@link ContractsAPI} first to use the {@link ContractDetails} type
*/
- getApplicableActions(contractId: ContractId, environment?: Environment): Promise;
+ getApplicableActions(
+ contractDetails: ContractDetails,
+ environment?: Environment
+ ): Promise;
/**
* Converts an {@link ApplicableAction} into an {@link ApplicableInput}.
@@ -131,18 +141,16 @@ export interface ApplyApplicableInputRequest {
* @hidden
*/
export function mkApplicableActionsAPI(
- restClient: RestClient,
- wallet: WalletAPI,
- contractDI: ContractsAPI
+ di: RestDI & WalletDI
): ApplicableActionsAPI {
- const di = mkGetApplicableActionsDI(restClient);
+ const getApplicableActionsDI = mkGetApplicableActionsDI(di.restClient);
async function mkFilter(): Promise;
async function mkFilter(contractDetails: ActiveContract): Promise;
async function mkFilter(
contractDetails?: ActiveContract
): Promise {
- const curriedFilter = await mkApplicableActionsFilter(wallet);
+ const curriedFilter = await mkApplicableActionsFilter(di.wallet);
if (contractDetails) {
return (action: ApplicableAction) => curriedFilter(action, contractDetails);
} else {
@@ -151,17 +159,25 @@ export function mkApplicableActionsAPI(
}
return {
- getInput: getApplicableInput(di),
+ getInput: getApplicableInput(getApplicableActionsDI),
simulateInput: simulateApplicableInput,
- getApplicableActions: getApplicableActions(di),
- applyInput: applyInput(contractDI),
+ getApplicableActions: getApplicableActions(getApplicableActionsDI),
+ applyInput: applyInput(applyInputs(di)),
mkFilter,
};
}
-function applyInput(contractDI: ContractsAPI) {
- return async function (contractId: ContractId, request: ApplyApplicableInputRequest): Promise {
- return contractDI.applyInputs(contractId, {
+export type ApplyInputDI = (
+ contractId: ContractId,
+ request: ApplyInputsRequest
+) => Promise;
+
+function applyInput(doApply: ApplyInputDI) {
+ return async function (
+ contractId: ContractId,
+ request: ApplyApplicableInputRequest
+ ): Promise {
+ return doApply(contractId, {
inputs: request.input.inputs,
tags: request.tags,
metadata: request.metadata,
@@ -179,13 +195,6 @@ function applyInput(contractDI: ContractsAPI) {
});
};
}
-/**
- * @category ApplicableActionsAPI
- */
-export interface GetApplicableActionsResponse {
- actions: ApplicableAction[];
- contractDetails: ContractDetails;
-}
type ActionApplicant = Party | "anybody";
@@ -454,6 +463,7 @@ async function computeEnvironment({ getRuntimeTip }: ChainTipDI, currentContract
return { timeInterval: { from: lowerBound, to: upperBound - 1n } };
}
+// FIXME: Refactor dependencies
/**
* @hidden
*/
@@ -463,20 +473,6 @@ export const mkGetApplicableActionsDI = (restClient: RestClient): GetApplicableA
// TODO: Add caching
return restClient.getContractSourceById({ contractSourceId });
},
- getContractDetails: async (contractId: ContractId) => {
- const contractDetails = await restClient.getContractById({ contractId });
- if (typeof contractDetails.state === "undefined" || typeof contractDetails.currentContract === "undefined") {
- return { type: "closed" };
- } else {
- return {
- type: "active",
- contractId,
- currentState: contractDetails.state,
- currentContract: contractDetails.currentContract,
- roleTokenMintingPolicyId: contractDetails.roleTokenMintingPolicyId,
- };
- }
- },
getRuntimeTip: async () => {
const status = await restClient.healthcheck();
return new Date(status.tips.runtimeChain.slotTimeUTC);
@@ -484,16 +480,18 @@ export const mkGetApplicableActionsDI = (restClient: RestClient): GetApplicableA
};
};
-type GetApplicableActionsDI = GetContinuationDI & GetContractDetailsDI & ChainTipDI;
+type GetApplicableActionsDI = GetContinuationDI & ChainTipDI;
/**
* @hidden
*/
export function getApplicableActions(di: GetApplicableActionsDI) {
- return async function (contractId: ContractId, environment?: Environment): Promise {
- const contractDetails = await di.getContractDetails(contractId);
+ return async function (
+ contractDetails: ContractDetails,
+ environment?: Environment
+ ): Promise {
// If the contract is closed there are no applicable actions
- if (contractDetails.type === "closed") return { contractDetails, actions: [] };
+ if (contractDetails.type === "closed") return [];
const env = environment ?? (await computeEnvironment(di, contractDetails.currentContract));
@@ -526,10 +524,7 @@ export function getApplicableActions(di: GetApplicableActionsDI) {
)
);
}
- return {
- contractDetails,
- actions: applicableActions,
- };
+ return applicableActions;
};
}
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index 82a9f3f2..d3d7533b 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -11,10 +11,30 @@ import {
applyInputs,
getInputHistory,
} from "./deprecated-contracts.js";
-import { SingleInputTx } from "@marlowe.io/language-core-v1/semantics";
-import { Contract, MarloweState } from "@marlowe.io/language-core-v1";
-import { RestDI } from "@marlowe.io/runtime-rest-client";
-import { WalletDI } from "@marlowe.io/wallet";
+import {
+ SingleInputTx,
+ TransactionSuccess,
+} from "@marlowe.io/language-core-v1/semantics";
+import {
+ ChosenNum,
+ Contract,
+ Environment,
+ MarloweState,
+} from "@marlowe.io/language-core-v1";
+import { RestClient, RestDI } from "@marlowe.io/runtime-rest-client";
+import { WalletAPI, WalletDI } from "@marlowe.io/wallet";
+import {
+ ApplicableAction,
+ ApplicableActionsFilter,
+ ApplicableInput,
+ ApplyApplicableInputRequest,
+ ApplyInputDI,
+ CanAdvance,
+ CanChoose,
+ CanDeposit,
+ CanNotify,
+} from "./applicable-actions.js";
+import * as Applicable from "./applicable-actions.js";
/**
*
@@ -46,6 +66,80 @@ export function mkContractsAPI(di: ContractsDI): ContractsAPI {
};
}
+export interface ApplicableActionsAPI {
+ compute(environment?: Environment): Promise;
+ toInput(
+ action: CanNotify | CanDeposit | CanAdvance
+ ): Promise;
+ toInput(action: CanChoose, chosenNum: ChosenNum): Promise;
+ simulate(input: ApplicableInput): Promise;
+ apply(req: ApplyApplicableInputRequest): Promise;
+ mkFilter(): Promise;
+}
+
+type GetContractDetailsDI = {
+ getContractDetails: () => Promise;
+};
+
+type ContractIdDI = {
+ contractId: ContractId;
+};
+
+function mkApplicableActionsAPI(
+ di: RestDI & WalletDI & GetContractDetailsDI & ContractIdDI
+): ApplicableActionsAPI {
+ // TODO: Revisit the DI for this function
+ const standaloneAPI = Applicable.mkApplicableActionsAPI(di);
+ const getActiveContractDetails = () =>
+ di.getContractDetails().then((details) => {
+ // TODO: improve error type
+ if (details.type !== "active") {
+ throw new Error("Contract is not active");
+ }
+ return details;
+ });
+ async function toInput(
+ action: CanNotify | CanDeposit | CanAdvance
+ ): Promise;
+ async function toInput(
+ action: CanChoose,
+ chosenNum: ChosenNum
+ ): Promise;
+ async function toInput(
+ action: ApplicableAction,
+ chosenNum?: ChosenNum
+ ): Promise {
+ const contractDetails = await getActiveContractDetails();
+ if (action.actionType === "Choice") {
+ return standaloneAPI.getInput(contractDetails, action, chosenNum!);
+ } else {
+ return standaloneAPI.getInput(contractDetails, action);
+ }
+ }
+
+ return {
+ compute: (environment) =>
+ di
+ .getContractDetails()
+ .then((details) =>
+ standaloneAPI.getApplicableActions(details, environment)
+ ),
+ toInput,
+ // NOTE: The original ApplicableActionsAPI does not use promises and assumes that the contract details were
+ // the same as the ones used to compute and toInput. This is not the case here as we are fetching the contract
+ // details each time. We might want to rethink the flow of this API.
+ simulate: (input) =>
+ getActiveContractDetails().then((details) =>
+ standaloneAPI.simulateInput(details, input)
+ ),
+ apply: (req) => standaloneAPI.applyInput(di.contractId, req),
+ mkFilter: () =>
+ getActiveContractDetails().then((details) =>
+ standaloneAPI.mkFilter(details)
+ ),
+ };
+}
+
/**
* TODO comment
* @category New ContractsAPI
@@ -55,7 +149,7 @@ export interface ContractInstanceAPI {
waitForConfirmation: () => Promise;
getContractDetails: () => Promise;
applyInputs(applyInputsRequest: ApplyInputsRequest): Promise;
- // TODO: ApplicableInputs
+ applicableActions: ApplicableActionsAPI;
/**
* Get a list of the applied inputs for the contract
*/
@@ -73,8 +167,13 @@ function mkContractInstanceAPI(
return di.wallet.waitConfirmation(contractCreationTxId);
},
getContractDetails: async () => {
- return getContractDetails(di, contractId);
+ return getContractDetails(di)(contractId);
},
+ applicableActions: mkApplicableActionsAPI({
+ ...di,
+ contractId,
+ getContractDetails: () => getContractDetails(di)(contractId),
+ }),
applyInputs: async (request) => {
return applyInputs(di)(contractId, request);
},
@@ -86,25 +185,24 @@ function mkContractInstanceAPI(
};
}
-async function getContractDetails(
- di: ContractsDI,
- contractId: ContractId
-): Promise {
- const contractDetails = await di.restClient.getContractById({ contractId });
- if (
- typeof contractDetails.state === "undefined" ||
- typeof contractDetails.currentContract === "undefined"
- ) {
- return { type: "closed" };
- } else {
- return {
- type: "active",
- contractId,
- currentState: contractDetails.state,
- currentContract: contractDetails.currentContract,
- roleTokenMintingPolicyId: contractDetails.roleTokenMintingPolicyId,
- };
- }
+function getContractDetails(di: ContractsDI) {
+ return async function (contractId: ContractId): Promise {
+ const contractDetails = await di.restClient.getContractById({ contractId });
+ if (
+ typeof contractDetails.state === "undefined" ||
+ typeof contractDetails.currentContract === "undefined"
+ ) {
+ return { type: "closed" };
+ } else {
+ return {
+ type: "active",
+ contractId,
+ currentState: contractDetails.state,
+ currentContract: contractDetails.currentContract,
+ roleTokenMintingPolicyId: contractDetails.roleTokenMintingPolicyId,
+ };
+ }
+ };
}
/**
@@ -133,11 +231,3 @@ export type ActiveContract = {
* @category New ContractsAPI
*/
export type ContractDetails = ClosedContract | ActiveContract;
-
-/**
- * TODO comment
- * @category New ContractsAPI
- */
-export type GetContractDetailsDI = {
- getContractDetails: (contractId: ContractId) => Promise;
-};
diff --git a/packages/runtime/lifecycle/src/generic/runtime.ts b/packages/runtime/lifecycle/src/generic/runtime.ts
index 9cb80402..b731f72a 100644
--- a/packages/runtime/lifecycle/src/generic/runtime.ts
+++ b/packages/runtime/lifecycle/src/generic/runtime.ts
@@ -27,7 +27,7 @@ export function mkRuntimeLifecycle(
applicableActions: mkApplicableActionsAPI(
restClient,
wallet,
- deprecatedContractAPI
+ deprecatedContractAPI.applyInputs
),
};
}
From 7634a433343bc8892e4ac46f022faa62c2ed1621 Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Fri, 8 Mar 2024 10:11:59 -0300
Subject: [PATCH 04/14] Refactor applicable actions nested API
---
.../src/experimental-features/source-map.ts | 19 +++-
examples/nodejs/src/marlowe-object-flow.ts | 59 ++++++-----
packages/runtime/lifecycle/src/api.ts | 4 +-
.../lifecycle/src/generic/new-contract-api.ts | 98 ++++++++++---------
.../runtime/lifecycle/src/generic/runtime.ts | 6 +-
5 files changed, 103 insertions(+), 83 deletions(-)
diff --git a/examples/nodejs/src/experimental-features/source-map.ts b/examples/nodejs/src/experimental-features/source-map.ts
index 2de01a38..b2c56628 100644
--- a/examples/nodejs/src/experimental-features/source-map.ts
+++ b/examples/nodejs/src/experimental-features/source-map.ts
@@ -1,7 +1,16 @@
import * as M from "fp-ts/lib/Map.js";
-import { ContractBundleMap, bundleMapToList, isAnnotated, stripAnnotations } from "@marlowe.io/marlowe-object";
-import { CreateContractRequestBase, RuntimeLifecycle } from "@marlowe.io/runtime-lifecycle/api";
+import {
+ ContractBundleMap,
+ bundleMapToList,
+ isAnnotated,
+ stripAnnotations,
+} from "@marlowe.io/marlowe-object";
+import {
+ ContractInstanceAPI,
+ CreateContractRequestBase,
+ RuntimeLifecycle,
+} from "@marlowe.io/runtime-lifecycle/api";
import { ContractClosure, getContractClosure } from "./contract-closure.js";
import * as Core from "@marlowe.io/language-core-v1";
@@ -154,7 +163,9 @@ export interface SourceMap {
closure: ContractClosure;
annotateHistory(history: SingleInputTx[]): SingleInputTx[];
playHistory(history: SingleInputTx[]): TransactionOutput;
- createContract(options: CreateContractRequestBase): Promise<[ContractId, TxId]>;
+ createContract(
+ options: CreateContractRequestBase
+ ): Promise;
contractInstanceOf(contractId: ContractId): Promise;
}
@@ -177,7 +188,7 @@ export async function mkSourceMap(
},
createContract: (options: CreateContractRequestBase) => {
const contract = stripAnnotations(closure.contracts.get(closure.main)!);
- return lifecycle.deprecatedContractAPI.createContract({
+ return lifecycle.newContractAPI.createContract({
...options,
contract,
});
diff --git a/examples/nodejs/src/marlowe-object-flow.ts b/examples/nodejs/src/marlowe-object-flow.ts
index 965926d4..9c5c4202 100644
--- a/examples/nodejs/src/marlowe-object-flow.ts
+++ b/examples/nodejs/src/marlowe-object-flow.ts
@@ -11,6 +11,7 @@
*/
import { mkLucidWallet, WalletAPI } from "@marlowe.io/wallet";
import { mkRuntimeLifecycle } from "@marlowe.io/runtime-lifecycle";
+import { ContractInstanceAPI } from "@marlowe.io/runtime-lifecycle/api";
import { Lucid, Blockfrost, C } from "lucid-cardano";
import { readConfig } from "./config.js";
import { datetoTimeout, When } from "@marlowe.io/language-core-v1";
@@ -18,6 +19,7 @@ import {
addressBech32,
contractId,
ContractId,
+ contractIdToTxId,
stakeAddressBech32,
StakeAddressBech32,
TxId,
@@ -171,7 +173,7 @@ async function createContractMenu(lifecycle: RuntimeLifecycle, rewardAddress?: S
};
const metadata = delayPaymentTemplate.toMetadata(scheme);
const sourceMap = await mkSourceMap(lifecycle, mkDelayPayment(scheme));
- const [contractId, txId] = await sourceMap.createContract({
+ const contractInstance = await sourceMap.createContract({
stakeAddress: rewardAddress,
tags: { DELAY_PAYMENT_VERSION: "2" },
metadata,
@@ -179,9 +181,12 @@ async function createContractMenu(lifecycle: RuntimeLifecycle, rewardAddress?: S
console.log(`Contract created with id ${contractId}`);
- await waitIndicator(lifecycle.wallet, txId);
+ await waitIndicator(
+ lifecycle.wallet,
+ contractIdToTxId(contractInstance.contractId)
+ );
- return contractMenu(lifecycle, scheme, sourceMap, contractId);
+ return contractMenu(lifecycle.wallet, contractInstance, scheme, sourceMap);
}
/**
@@ -211,39 +216,43 @@ async function loadContractMenu(lifecycle: RuntimeLifecycle) {
console.log(` * Pay from: ${validationResult.scheme.payer}`);
console.log(` * Pay to: ${validationResult.scheme.payee}`);
console.log(` * Amount: ${validationResult.scheme.amount} lovelaces`);
- console.log(` * Deposit deadline: ${validationResult.scheme.depositDeadline}`);
- console.log(` * Release deadline: ${validationResult.scheme.releaseDeadline}`);
-
- return contractMenu(lifecycle, validationResult.scheme, validationResult.sourceMap, cid);
+ console.log(
+ ` * Deposit deadline: ${validationResult.scheme.depositDeadline}`
+ );
+ console.log(
+ ` * Release deadline: ${validationResult.scheme.releaseDeadline}`
+ );
+ const contractInstance = await lifecycle.newContractAPI.loadContract(cid);
+ return contractMenu(
+ lifecycle.wallet,
+ contractInstance,
+ validationResult.scheme,
+ validationResult.sourceMap
+ );
}
/**
* This is an Inquirer.js flow to interact with a contract
*/
async function contractMenu(
- lifecycle: RuntimeLifecycle,
+ wallet: WalletAPI,
+ contractInstance: ContractInstanceAPI,
scheme: DelayPaymentParameters,
- sourceMap: SourceMap,
- contractId: ContractId
+ sourceMap: SourceMap
): Promise {
// Get and print the contract logical state.
- const inputHistory =
- await lifecycle.deprecatedContractAPI.getInputHistory(contractId);
+
+ const inputHistory = await contractInstance.getInputHistory();
const contractState = getState(
datetoTimeout(new Date()),
inputHistory,
sourceMap
);
+ if (contractState.type === "Closed") return;
printState(contractState, scheme);
-
// See what actions are applicable to the current contract state
- const { contractDetails, actions } = await lifecycle.applicableActions.getApplicableActions(contractId);
-
- if (contractDetails.type === "closed") return;
-
- const myActionsFilter = await lifecycle.applicableActions.mkFilter(contractDetails);
- const myActions = actions.filter(myActionsFilter);
+ const applicableActions = await contractInstance.computeApplicableActions();
const choices: Array<{
name: string;
@@ -253,7 +262,7 @@ async function contractMenu(
name: "Re-check contract state",
value: { actionType: "check-state" },
},
- ...myActions.map((action) => {
+ ...applicableActions.myActions.map((action) => {
switch (action.actionType) {
case "Advance":
return {
@@ -286,19 +295,19 @@ async function contractMenu(
});
switch (selectedAction.actionType) {
case "check-state":
- return contractMenu(lifecycle, scheme, sourceMap, contractId);
+ return contractMenu(wallet, contractInstance, scheme, sourceMap);
case "return":
return;
case "Advance":
case "Deposit":
console.log("Applying input");
- const applicableInput = await lifecycle.applicableActions.getInput(contractDetails, selectedAction);
- const txId = await lifecycle.applicableActions.applyInput(contractId, {
+ const applicableInput = await applicableActions.toInput(selectedAction);
+ const txId = await applicableActions.apply({
input: applicableInput,
});
console.log(`Input applied with txId ${txId}`);
- await waitIndicator(lifecycle.wallet, txId);
- return contractMenu(lifecycle, scheme, sourceMap, contractId);
+ await waitIndicator(wallet, txId);
+ return contractMenu(wallet, contractInstance, scheme, sourceMap);
}
}
diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts
index c4c22239..f2a717f5 100644
--- a/packages/runtime/lifecycle/src/api.ts
+++ b/packages/runtime/lifecycle/src/api.ts
@@ -23,12 +23,12 @@ import {
CanChoose,
CanDeposit,
CanNotify,
- GetApplicableActionsResponse,
} from "./generic/applicable-actions.js";
import {
ActiveContract,
ClosedContract,
ContractDetails,
+ ContractInstanceAPI,
} from "./generic/new-contract-api.js";
import {
ContractsAPI,
@@ -45,10 +45,10 @@ export {
CanChoose,
CanDeposit,
CanNotify,
- GetApplicableActionsResponse,
ActiveContract,
ClosedContract,
ContractDetails,
+ ContractInstanceAPI,
CreateContractRequestBase,
};
import * as NewContract from "./generic/new-contract-api.js";
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index d3d7533b..c29d9701 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -55,6 +55,7 @@ export interface ContractsAPI {
}
export function mkContractsAPI(di: ContractsDI): ContractsAPI {
+ // TODO: Cache of API's
return {
createContract: async (request) => {
const [contractId, _] = await createContract(di)(request);
@@ -67,37 +68,32 @@ export function mkContractsAPI(di: ContractsDI): ContractsAPI {
}
export interface ApplicableActionsAPI {
- compute(environment?: Environment): Promise;
+ actions: ApplicableAction[];
+ myActions: ApplicableAction[];
toInput(
action: CanNotify | CanDeposit | CanAdvance
): Promise;
toInput(action: CanChoose, chosenNum: ChosenNum): Promise;
- simulate(input: ApplicableInput): Promise;
+ simulate(input: ApplicableInput): TransactionSuccess;
apply(req: ApplyApplicableInputRequest): Promise;
- mkFilter(): Promise;
}
-type GetContractDetailsDI = {
- getContractDetails: () => Promise;
-};
-
-type ContractIdDI = {
- contractId: ContractId;
-};
-
function mkApplicableActionsAPI(
- di: RestDI & WalletDI & GetContractDetailsDI & ContractIdDI
+ di: RestDI & WalletDI,
+ actions: ApplicableAction[],
+ myActions: ApplicableAction[],
+ contractDetails: ContractDetails,
+ contractId: ContractId
): ApplicableActionsAPI {
// TODO: Revisit the DI for this function
const standaloneAPI = Applicable.mkApplicableActionsAPI(di);
- const getActiveContractDetails = () =>
- di.getContractDetails().then((details) => {
- // TODO: improve error type
- if (details.type !== "active") {
- throw new Error("Contract is not active");
- }
- return details;
- });
+ const getActiveContractDetails = () => {
+ if (contractDetails.type !== "active") {
+ throw new Error("Contract is not active");
+ }
+ return contractDetails;
+ };
+
async function toInput(
action: CanNotify | CanDeposit | CanAdvance
): Promise;
@@ -109,34 +105,23 @@ function mkApplicableActionsAPI(
action: ApplicableAction,
chosenNum?: ChosenNum
): Promise {
- const contractDetails = await getActiveContractDetails();
+ const activeContractDetails = getActiveContractDetails();
if (action.actionType === "Choice") {
- return standaloneAPI.getInput(contractDetails, action, chosenNum!);
+ return standaloneAPI.getInput(activeContractDetails, action, chosenNum!);
} else {
- return standaloneAPI.getInput(contractDetails, action);
+ return standaloneAPI.getInput(activeContractDetails, action);
}
}
return {
- compute: (environment) =>
- di
- .getContractDetails()
- .then((details) =>
- standaloneAPI.getApplicableActions(details, environment)
- ),
+ actions,
+ myActions,
toInput,
- // NOTE: The original ApplicableActionsAPI does not use promises and assumes that the contract details were
- // the same as the ones used to compute and toInput. This is not the case here as we are fetching the contract
- // details each time. We might want to rethink the flow of this API.
- simulate: (input) =>
- getActiveContractDetails().then((details) =>
- standaloneAPI.simulateInput(details, input)
- ),
- apply: (req) => standaloneAPI.applyInput(di.contractId, req),
- mkFilter: () =>
- getActiveContractDetails().then((details) =>
- standaloneAPI.mkFilter(details)
- ),
+ simulate: (input) => {
+ const activeContractDetails = getActiveContractDetails();
+ return standaloneAPI.simulateInput(activeContractDetails, input);
+ },
+ apply: (req) => standaloneAPI.applyInput(contractId, req),
};
}
@@ -149,7 +134,9 @@ export interface ContractInstanceAPI {
waitForConfirmation: () => Promise;
getContractDetails: () => Promise;
applyInputs(applyInputsRequest: ApplyInputsRequest): Promise;
- applicableActions: ApplicableActionsAPI;
+ computeApplicableActions(
+ environment?: Environment
+ ): Promise;
/**
* Get a list of the applied inputs for the contract
*/
@@ -161,6 +148,7 @@ function mkContractInstanceAPI(
contractId: ContractId
): ContractInstanceAPI {
const contractCreationTxId = contractIdToTxId(contractId);
+ const applicableActionsAPI = Applicable.mkApplicableActionsAPI(di);
return {
contractId,
waitForConfirmation: async () => {
@@ -169,11 +157,27 @@ function mkContractInstanceAPI(
getContractDetails: async () => {
return getContractDetails(di)(contractId);
},
- applicableActions: mkApplicableActionsAPI({
- ...di,
- contractId,
- getContractDetails: () => getContractDetails(di)(contractId),
- }),
+ computeApplicableActions: async (env) => {
+ const contractDetails = await getContractDetails(di)(contractId);
+ const actions = await applicableActionsAPI.getApplicableActions(
+ contractDetails,
+ env
+ );
+ let myActions = [] as ApplicableAction[];
+ if (contractDetails.type === "active") {
+ const myActionsFilter =
+ await applicableActionsAPI.mkFilter(contractDetails);
+ myActions = actions.filter(myActionsFilter);
+ }
+
+ return mkApplicableActionsAPI(
+ di,
+ actions,
+ myActions,
+ contractDetails,
+ contractId
+ );
+ },
applyInputs: async (request) => {
return applyInputs(di)(contractId, request);
},
diff --git a/packages/runtime/lifecycle/src/generic/runtime.ts b/packages/runtime/lifecycle/src/generic/runtime.ts
index b731f72a..938f729f 100644
--- a/packages/runtime/lifecycle/src/generic/runtime.ts
+++ b/packages/runtime/lifecycle/src/generic/runtime.ts
@@ -24,10 +24,6 @@ export function mkRuntimeLifecycle(
deprecatedContractAPI,
newContractAPI: NewContract.mkContractsAPI({ wallet, restClient }),
payouts: mkPayoutLifecycle(wallet, deprecatedRestAPI, restClient),
- applicableActions: mkApplicableActionsAPI(
- restClient,
- wallet,
- deprecatedContractAPI.applyInputs
- ),
+ applicableActions: mkApplicableActionsAPI({ restClient, wallet }),
};
}
From 3a29a1bf79f662a518c23d2d096a27d4f27b63cb Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Fri, 8 Mar 2024 10:15:16 -0300
Subject: [PATCH 05/14] Add cache of ContractInstanceAPIs
---
.../lifecycle/src/generic/new-contract-api.ts | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index c29d9701..3b0de110 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -55,14 +55,21 @@ export interface ContractsAPI {
}
export function mkContractsAPI(di: ContractsDI): ContractsAPI {
- // TODO: Cache of API's
+ // The ContractInstance API is stateful as it has some cache, so whenever
+ // possible we want to reuse the same instance of the API for the same contractId
+ const apis = new Map();
return {
createContract: async (request) => {
const [contractId, _] = await createContract(di)(request);
- return mkContractInstanceAPI(di, contractId);
+ apis.set(contractId, mkContractInstanceAPI(di, contractId));
+ return apis.get(contractId)!;
},
loadContract: async (contractId) => {
- return mkContractInstanceAPI(di, contractId);
+ if (apis.has(contractId)) {
+ return apis.get(contractId)!;
+ } else {
+ return mkContractInstanceAPI(di, contractId);
+ }
},
};
}
From 134be9cf424c69e6e348bc59f1e0633c96a4db0a Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Fri, 8 Mar 2024 10:20:55 -0300
Subject: [PATCH 06/14] Allow reuse of contractDetails while computing
applicable actions
---
.../lifecycle/src/generic/new-contract-api.ts | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index 3b0de110..70986e1b 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -132,6 +132,15 @@ function mkApplicableActionsAPI(
};
}
+/**
+ * TODO comment
+ * @category New ContractsAPI
+ */
+type ComputeApplicableActionsRequest = {
+ environment?: Environment;
+ contractDetails?: ContractDetails;
+};
+
/**
* TODO comment
* @category New ContractsAPI
@@ -142,7 +151,7 @@ export interface ContractInstanceAPI {
getContractDetails: () => Promise;
applyInputs(applyInputsRequest: ApplyInputsRequest): Promise;
computeApplicableActions(
- environment?: Environment
+ request?: ComputeApplicableActionsRequest
): Promise;
/**
* Get a list of the applied inputs for the contract
@@ -164,11 +173,12 @@ function mkContractInstanceAPI(
getContractDetails: async () => {
return getContractDetails(di)(contractId);
},
- computeApplicableActions: async (env) => {
- const contractDetails = await getContractDetails(di)(contractId);
+ computeApplicableActions: async (req = {}) => {
+ const contractDetails =
+ req.contractDetails ?? (await getContractDetails(di)(contractId));
const actions = await applicableActionsAPI.getApplicableActions(
contractDetails,
- env
+ req.environment
);
let myActions = [] as ApplicableAction[];
if (contractDetails.type === "active") {
From f177b8230d2cd1290f14588baba74c30399a608c Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Fri, 8 Mar 2024 11:24:49 -0300
Subject: [PATCH 07/14] Renamed deprecatedContractsAPI back to its original
name
---
examples/nodejs/src/escrow-flow.ts | 15 ++++++---------
packages/runtime/lifecycle/src/api.ts | 4 ++--
.../lifecycle/src/generic/applicable-actions.ts | 6 +-----
.../{deprecated-contracts.ts => contracts.ts} | 0
.../lifecycle/src/generic/new-contract-api.ts | 2 +-
packages/runtime/lifecycle/src/generic/runtime.ts | 10 +++-------
6 files changed, 13 insertions(+), 24 deletions(-)
rename packages/runtime/lifecycle/src/generic/{deprecated-contracts.ts => contracts.ts} (100%)
diff --git a/examples/nodejs/src/escrow-flow.ts b/examples/nodejs/src/escrow-flow.ts
index a8da5c6c..09bbc2c7 100644
--- a/examples/nodejs/src/escrow-flow.ts
+++ b/examples/nodejs/src/escrow-flow.ts
@@ -88,17 +88,14 @@ async function main(action: "buy" | "sell", otherAddress: string, amount: number
console.log("Mediator: " + Mediator);
console.log("Amount: " + amount);
- const [contractId, txId] = await runtime.deprecatedContractAPI.createContract(
- {
- contract: escrow,
- roles: { Buyer, Seller, Mediator },
- }
- );
+ const contractInstance = await runtime.newContractAPI.createContract({
+ contract: escrow,
+ roles: { Buyer, Seller, Mediator },
+ });
- console.log("Contract ID: " + contractId);
- console.log("Transaction ID: " + txId);
+ console.log("Contract ID: " + contractInstance.contractId);
console.log("Waiting for confirmation...");
- await wallet.waitConfirmation(txId);
+ await contractInstance.waitForConfirmation();
console.log("Contract created successfully!");
}
diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts
index f2a717f5..93ad387a 100644
--- a/packages/runtime/lifecycle/src/api.ts
+++ b/packages/runtime/lifecycle/src/api.ts
@@ -33,7 +33,7 @@ import {
import {
ContractsAPI,
CreateContractRequestBase,
-} from "./generic/deprecated-contracts.js";
+} from "./generic/contracts.js";
export {
ApplicableActionsAPI,
ApplicableAction,
@@ -74,7 +74,7 @@ export interface RuntimeLifecycle {
/**
* The contracts API is a high level API that lets you create and interact with Marlowe contracts.
*/
- deprecatedContractAPI: ContractsAPI;
+ contracts: ContractsAPI;
payouts: PayoutsAPI;
applicableActions: ApplicableActionsAPI;
}
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index 2e94a6ff..f7c490d3 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -1,8 +1,4 @@
-import {
- ApplyInputsRequest,
- ContractsAPI,
- applyInputs,
-} from "./deprecated-contracts.js";
+import { ApplyInputsRequest, ContractsAPI, applyInputs } from "./contracts.js";
import { Monoid } from "fp-ts/lib/Monoid.js";
import * as R from "fp-ts/lib/Record.js";
diff --git a/packages/runtime/lifecycle/src/generic/deprecated-contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts
similarity index 100%
rename from packages/runtime/lifecycle/src/generic/deprecated-contracts.ts
rename to packages/runtime/lifecycle/src/generic/contracts.ts
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index 70986e1b..9a9f68c6 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -10,7 +10,7 @@ import {
createContract,
applyInputs,
getInputHistory,
-} from "./deprecated-contracts.js";
+} from "./contracts.js";
import {
SingleInputTx,
TransactionSuccess,
diff --git a/packages/runtime/lifecycle/src/generic/runtime.ts b/packages/runtime/lifecycle/src/generic/runtime.ts
index 938f729f..75c0c645 100644
--- a/packages/runtime/lifecycle/src/generic/runtime.ts
+++ b/packages/runtime/lifecycle/src/generic/runtime.ts
@@ -4,7 +4,7 @@ import { WalletAPI } from "@marlowe.io/wallet/api";
import { FPTSRestAPI, RestClient } from "@marlowe.io/runtime-rest-client";
import { mkPayoutLifecycle } from "./payouts.js";
-import { mkContractLifecycle } from "./deprecated-contracts.js";
+import { mkContractLifecycle } from "./contracts.js";
import { mkApplicableActionsAPI } from "./applicable-actions.js";
import * as NewContract from "./new-contract-api.js";
@@ -13,15 +13,11 @@ export function mkRuntimeLifecycle(
restClient: RestClient,
wallet: WalletAPI
): RuntimeLifecycle {
- const deprecatedContractAPI = mkContractLifecycle(
- wallet,
- deprecatedRestAPI,
- restClient
- );
+ const contracts = mkContractLifecycle(wallet, deprecatedRestAPI, restClient);
return {
wallet: wallet,
restClient,
- deprecatedContractAPI,
+ contracts,
newContractAPI: NewContract.mkContractsAPI({ wallet, restClient }),
payouts: mkPayoutLifecycle(wallet, deprecatedRestAPI, restClient),
applicableActions: mkApplicableActionsAPI({ restClient, wallet }),
From 3d6b62d3b20be268e7ff88a96435b1c487c57d8b Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Fri, 8 Mar 2024 13:03:20 -0300
Subject: [PATCH 08/14] Refactor some of the DI
---
.../src/generic/applicable-actions.ts | 40 +++++--------------
.../lifecycle/src/generic/new-contract-api.ts | 14 ++++---
.../runtime/lifecycle/src/generic/runtime.ts | 31 ++++++++++++--
3 files changed, 47 insertions(+), 38 deletions(-)
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index f7c490d3..26cd1a43 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -59,11 +59,6 @@ export interface ApplicableActionsAPI {
* the environment is computed using the runtime tip as a lower bound and the next timeout
* as an upper bound.
* @returns An object with an array of {@link ApplicableAction | applicable actions} and the {@link ContractDetails | contract details}
- * @experimental
- * @remarks
- * **EXPERIMENTAL:** Perhaps instead of receiving a contractId and returning the actions and contractDetails this
- * function should receive the contractDetails and just return the actions.
- * To do this, we should refactor the {@link ContractsAPI} first to use the {@link ContractDetails} type
*/
getApplicableActions(
contractDetails: ContractDetails,
@@ -137,10 +132,8 @@ export interface ApplyApplicableInputRequest {
* @hidden
*/
export function mkApplicableActionsAPI(
- di: RestDI & WalletDI
+ di: RestDI & WalletDI & GetContinuationDI & ChainTipDI
): ApplicableActionsAPI {
- const getApplicableActionsDI = mkGetApplicableActionsDI(di.restClient);
-
async function mkFilter(): Promise;
async function mkFilter(contractDetails: ActiveContract): Promise;
async function mkFilter(
@@ -155,9 +148,9 @@ export function mkApplicableActionsAPI(
}
return {
- getInput: getApplicableInput(getApplicableActionsDI),
+ getInput: getApplicableInput(di),
simulateInput: simulateApplicableInput,
- getApplicableActions: getApplicableActions(getApplicableActionsDI),
+ getApplicableActions: getApplicableActions(di),
applyInput: applyInput(applyInputs(di)),
mkFilter,
};
@@ -430,7 +423,10 @@ function getApplicant(action: ApplicableAction): ActionApplicant {
}
}
-type ChainTipDI = {
+/**
+ * @hidden
+ */
+export type ChainTipDI = {
getRuntimeTip: () => Promise;
};
@@ -459,23 +455,6 @@ async function computeEnvironment({ getRuntimeTip }: ChainTipDI, currentContract
return { timeInterval: { from: lowerBound, to: upperBound - 1n } };
}
-// FIXME: Refactor dependencies
-/**
- * @hidden
- */
-export const mkGetApplicableActionsDI = (restClient: RestClient): GetApplicableActionsDI => {
- return {
- getContractContinuation: (contractSourceId: ContractSourceId) => {
- // TODO: Add caching
- return restClient.getContractSourceById({ contractSourceId });
- },
- getRuntimeTip: async () => {
- const status = await restClient.healthcheck();
- return new Date(status.tips.runtimeChain.slotTimeUTC);
- },
- };
-};
-
type GetApplicableActionsDI = GetContinuationDI & ChainTipDI;
/**
@@ -719,7 +698,10 @@ const mergeApplicableActionAccumulator: Monoid = {
},
};
-type GetContinuationDI = {
+/**
+ * @hidden
+ */
+export type GetContinuationDI = {
getContractContinuation: (sourceId: ContractSourceId) => Promise;
};
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index 9a9f68c6..d98ee9a0 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -33,15 +33,18 @@ import {
CanChoose,
CanDeposit,
CanNotify,
+ ChainTipDI,
+ GetContinuationDI,
} from "./applicable-actions.js";
import * as Applicable from "./applicable-actions.js";
+import { ContractSourceId } from "@marlowe.io/marlowe-object";
/**
*
* @description Dependency Injection for the Contract API
* @hidden
*/
-export type ContractsDI = WalletDI & RestDI;
+export type ContractsDI = WalletDI & RestDI & GetContinuationDI & ChainTipDI;
/**
* TODO comment
@@ -58,6 +61,7 @@ export function mkContractsAPI(di: ContractsDI): ContractsAPI {
// The ContractInstance API is stateful as it has some cache, so whenever
// possible we want to reuse the same instance of the API for the same contractId
const apis = new Map();
+
return {
createContract: async (request) => {
const [contractId, _] = await createContract(di)(request);
@@ -86,14 +90,12 @@ export interface ApplicableActionsAPI {
}
function mkApplicableActionsAPI(
- di: RestDI & WalletDI,
+ di: RestDI & WalletDI & GetContinuationDI & ChainTipDI,
actions: ApplicableAction[],
myActions: ApplicableAction[],
contractDetails: ContractDetails,
contractId: ContractId
): ApplicableActionsAPI {
- // TODO: Revisit the DI for this function
- const standaloneAPI = Applicable.mkApplicableActionsAPI(di);
const getActiveContractDetails = () => {
if (contractDetails.type !== "active") {
throw new Error("Contract is not active");
@@ -101,6 +103,8 @@ function mkApplicableActionsAPI(
return contractDetails;
};
+ const standaloneAPI = Applicable.mkApplicableActionsAPI(di);
+
async function toInput(
action: CanNotify | CanDeposit | CanAdvance
): Promise;
@@ -160,7 +164,7 @@ export interface ContractInstanceAPI {
}
function mkContractInstanceAPI(
- di: ContractsDI,
+ di: ContractsDI & GetContinuationDI & ChainTipDI,
contractId: ContractId
): ContractInstanceAPI {
const contractCreationTxId = contractIdToTxId(contractId);
diff --git a/packages/runtime/lifecycle/src/generic/runtime.ts b/packages/runtime/lifecycle/src/generic/runtime.ts
index 75c0c645..deadfe3a 100644
--- a/packages/runtime/lifecycle/src/generic/runtime.ts
+++ b/packages/runtime/lifecycle/src/generic/runtime.ts
@@ -1,8 +1,9 @@
-import { RuntimeLifecycle } from "../api.js";
import { WalletAPI } from "@marlowe.io/wallet/api";
-
import { FPTSRestAPI, RestClient } from "@marlowe.io/runtime-rest-client";
+import { Contract } from "@marlowe.io/language-core-v1";
+import { ContractSourceId } from "@marlowe.io/marlowe-object";
+import { RuntimeLifecycle } from "../api.js";
import { mkPayoutLifecycle } from "./payouts.js";
import { mkContractLifecycle } from "./contracts.js";
import { mkApplicableActionsAPI } from "./applicable-actions.js";
@@ -14,12 +15,34 @@ export function mkRuntimeLifecycle(
wallet: WalletAPI
): RuntimeLifecycle {
const contracts = mkContractLifecycle(wallet, deprecatedRestAPI, restClient);
+ // We cache the contract sources when the API is asking for continuations
+ const sources = new Map();
+
+ const di = {
+ wallet,
+ restClient,
+ getContractContinuation: async (contractSourceId: ContractSourceId) => {
+ if (sources.has(contractSourceId)) {
+ return sources.get(contractSourceId)!;
+ } else {
+ const contract = await restClient.getContractSourceById({
+ contractSourceId,
+ });
+ sources.set(contractSourceId, contract);
+ return contract;
+ }
+ },
+ getRuntimeTip: async () => {
+ const status = await restClient.healthcheck();
+ return new Date(status.tips.runtimeChain.slotTimeUTC);
+ },
+ };
return {
wallet: wallet,
restClient,
contracts,
- newContractAPI: NewContract.mkContractsAPI({ wallet, restClient }),
+ newContractAPI: NewContract.mkContractsAPI(di),
payouts: mkPayoutLifecycle(wallet, deprecatedRestAPI, restClient),
- applicableActions: mkApplicableActionsAPI({ restClient, wallet }),
+ applicableActions: mkApplicableActionsAPI(di),
};
}
From 5e12fbf48a33961013344d4ac81d9b84022b554c Mon Sep 17 00:00:00 2001
From: Hernan Rajchert
Date: Fri, 15 Mar 2024 13:43:15 -0300
Subject: [PATCH 09/14] Improve dynamic strict checking
---
examples/rest-client-flow/index.html | 19 +-
packages/adapter/src/io-ts.ts | 37 +-
packages/runtime/client/rest/src/index.ts | 568 +++++++++---------
.../runtime/lifecycle/src/browser/index.ts | 33 +-
packages/runtime/lifecycle/src/index.ts | 30 +-
.../runtime/lifecycle/src/nodejs/index.ts | 19 +-
6 files changed, 350 insertions(+), 356 deletions(-)
diff --git a/examples/rest-client-flow/index.html b/examples/rest-client-flow/index.html
index 6ce76b04..ced4ce46 100644
--- a/examples/rest-client-flow/index.html
+++ b/examples/rest-client-flow/index.html
@@ -25,7 +25,13 @@
Request
This should be filled with a JSON object that starts with an array, where each element is a numbered
parameter.
-
+
@@ -148,7 +154,7 @@
Console
switch (action) {
case "getContracts":
log(`Getting contracts from ${H.getRuntimeUrl()}`);
- result = await restClient.getContracts(...params);
+ result = await restClient.getContracts(params);
console.log("Contracts", result);
const nextRange = result.nextRange?.value ?? "-";
const prevRange = result.prevRange?.value ?? "-";
@@ -158,12 +164,12 @@
Console
break;
case "submitContract":
log(`Submitting contract on ${H.getRuntimeUrl()}`);
- await restClient.submitContract(...getParams());
+ await restClient.submitContract(getParams());
log(`Done`);
break;
default:
log(`Calling ${action} on ${H.getRuntimeUrl()}`);
- result = await restClient[action](...params);
+ result = await restClient[action](params);
logJSON("Result:", result);
}
} catch (e) {
@@ -182,10 +188,7 @@
Console
try {
params = JSON.parse(jsonParams);
} catch (e) {
- throw new Error("Parameters must be a valid JSON array: " + e);
- }
- if (!Array.isArray(params)) {
- throw new Error("Parameters must be an array");
+ throw new Error("Parameters must be a valid JSON: " + e);
}
return params;
}
diff --git a/packages/adapter/src/io-ts.ts b/packages/adapter/src/io-ts.ts
index 0f98c761..5d1344bc 100644
--- a/packages/adapter/src/io-ts.ts
+++ b/packages/adapter/src/io-ts.ts
@@ -5,6 +5,8 @@ import { Errors } from "io-ts/lib/index.js";
import { Refinement } from "fp-ts/lib/Refinement.js";
import { pipe } from "fp-ts/lib/function.js";
import * as Either from "fp-ts/lib/Either.js";
+import * as BigIntReporter from "jsonbigint-io-ts-reporters";
+
/**
* In the TS-SDK we duplicate the type and guard definition for each type as the
* inferred type from io-ts does not produce a good type export when used with
@@ -88,20 +90,43 @@ export function expectType(guard: t.Type, aValue: unknown): T {
}
/**
- * A mechanism for validating the type of a strict in a dynamically type context.
- * @param strict Whether to perform runtime checking to provide helpful error messages. May have a slight negative performance impact.
+ * Formats validation errors into a string.
+ * @param errors - The validation errors to format.
+ * @returns A string representation of the validation errors.
*/
-export function strictDynamicTypeCheck(strict: unknown): strict is boolean {
- return typeof strict === "boolean";
+export function formatValidationErrors(errors: Errors): string {
+ return BigIntReporter.formatValidationErrors(errors, {
+ truncateLongTypes: true,
+ }).join("\n");
+}
+
+export function dynamicAssertType(
+ guard: G,
+ value: unknown,
+ message?: string
+): t.TypeOf {
+ const result = guard.decode(value);
+ if (Either.isLeft(result)) {
+ throw new InvalidTypeError(guard, value, result.left, message);
+ }
+ return result.right;
}
+/**
+ * This error is thrown when we are dynamicly checking for the type of a value and the type is not
+ * the expected one.
+ */
export class InvalidTypeError extends Error {
constructor(
+ public readonly guard: t.Any,
+ public readonly value: unknown,
public readonly errors: Errors,
- public readonly value: any,
message?: string
) {
- super(message);
+ const msg =
+ message ??
+ `Unexpected type for value:\n${formatValidationErrors(errors)}`;
+ super(msg);
}
}
diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts
index ff976f87..fc4922b4 100644
--- a/packages/runtime/client/rest/src/index.ts
+++ b/packages/runtime/client/rest/src/index.ts
@@ -30,8 +30,11 @@ import { unsafeTaskEither } from "@marlowe.io/adapter/fp-ts";
import { ContractDetails } from "./contract/details.js";
import { TransactionDetails } from "./contract/transaction/details.js";
import { RuntimeStatus, healthcheck } from "./runtime/status.js";
-import { CompatibleRuntimeVersionGuard, RuntimeVersion } from "./runtime/version.js";
-import { InvalidTypeError, strictDynamicTypeCheck } from "@marlowe.io/adapter/io-ts";
+import {
+ CompatibleRuntimeVersionGuard,
+ RuntimeVersion,
+} from "./runtime/version.js";
+import { dynamicAssertType } from "@marlowe.io/adapter/io-ts";
export { Page, ItemRange, ItemRangeGuard, ItemRangeBrand, PageGuard } from "./pagination.js";
@@ -213,43 +216,31 @@ export interface RestClient {
getPayoutById(request: Payout.GetPayoutByIdRequest): Promise;
}
-function mkRestClientArgumentDynamicTypeCheck(baseURL: unknown, strict: boolean): baseURL is string {
- return strict ? typeof baseURL === "string" : true;
-}
-
-function withDynamicTypeCheck(
- arg: any,
- decode: (x: any) => t.Validation,
+function withDynamicTypeCheck(
strict: boolean,
- cont: (x: T) => G
-): G {
- if (strict) {
- const result = decode(arg);
- if (result._tag === "Left") throw new InvalidTypeError(result.left, arg, "Invalid argument");
- }
- return cont(arg);
+ requestGuard: t.Type,
+ doRequest: (x: Request) => Response
+) {
+ return (request: unknown): Response => {
+ if (strict) {
+ const validatedRequest = dynamicAssertType(requestGuard, request);
+
+ return doRequest(validatedRequest);
+ } else {
+ return doRequest(request as Request);
+ }
+ };
}
-/**
- * Instantiates a REST client for the Marlowe API.
- * @param baseURL An http url pointing to the Marlowe API.
- * @see {@link https://github.com/input-output-hk/marlowe-starter-kit#quick-overview} To get a Marlowe runtime instance up and running.
- */
-export function mkRestClient(baseURL: string): RestClient;
/**
* Instantiates a REST client for the Marlowe API.
* @param baseURL An http url pointing to the Marlowe API.
* @param strict Whether to perform runtime checking to provide helpful error messages. May have a slight negative performance impact. Default value is `true`.
* @see {@link https://github.com/input-output-hk/marlowe-starter-kit#quick-overview} To get a Marlowe runtime instance up and running.
*/
-export function mkRestClient(baseURL: string, strict: boolean): RestClient;
-export function mkRestClient(baseURL: unknown, strict: unknown = true): RestClient {
- if (!strictDynamicTypeCheck(strict)) {
- throw new InvalidTypeError([], `Invalid type for argument 'strict', expected boolean but got ${strict}`);
- }
- if (!mkRestClientArgumentDynamicTypeCheck(baseURL, strict)) {
- throw new InvalidTypeError([], `Invalid type for argument 'baseURL', expected string but got ${baseURL}`);
- }
+export function mkRestClient(baseURL: string, strict = true): RestClient {
+ dynamicAssertType(t.boolean, strict, "Strict should be a boolean");
+ dynamicAssertType(t.string, baseURL, "Base URL should be a string");
const axiosInstance = axios.create({
baseURL,
@@ -271,280 +262,259 @@ export function mkRestClient(baseURL: unknown, strict: unknown = true): RestClie
version() {
return healthcheck(axiosInstance).then((status) => status.version);
},
- getContracts(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Contracts.GetContractsRequestGuard.decode(x),
- strict,
- (request) => {
- const range = request?.range;
- const tags = request?.tags ?? [];
- const partyAddresses = request?.partyAddresses ?? [];
- const partyRoles = request?.partyRoles ?? [];
- return unsafeTaskEither(
- Contracts.getHeadersByRangeViaAxios(axiosInstance)(range)({
- tags,
- partyAddresses,
- partyRoles,
- })
- );
- }
- );
- },
- getContractById(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Contract.GetContractByIdRequest.decode(x),
- strict,
- (request) => {
- return Contract.getContractById(axiosInstance, request.contractId);
- }
- );
- },
- async buildCreateContractTx(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Contracts.BuildCreateContractTxRequestGuard.decode(x),
- strict,
- async (request) => {
- const version = await runtimeVersion;
- // NOTE: Runtime 0.0.5 requires an explicit minUTxODeposit, but 0.0.6 and forward allows that field as optional
- // and it will calculate the actual minimum required. We use the version of the runtime to determine
- // if we use a "safe" default that is bigger than needed.
- const minUTxODeposit = request.minimumLovelaceUTxODeposit ?? (version === "0.0.5" ? 3000000 : undefined);
- const postContractsRequest = {
- contract: "contract" in request ? request.contract : request.sourceId,
- version: request.version,
- metadata: request.metadata ?? {},
- tags: request.tags ?? {},
- minUTxODeposit,
- roles: request.roles,
- threadRoleName: request.threadRoleName,
- };
- const addressesAndCollaterals = {
- changeAddress: request.changeAddress,
+ getContracts: withDynamicTypeCheck(
+ strict,
+ Contracts.GetContractsRequestGuard,
+ (request) => {
+ const range = request?.range;
+ const tags = request?.tags ?? [];
+ const partyAddresses = request?.partyAddresses ?? [];
+ const partyRoles = request?.partyRoles ?? [];
+ return unsafeTaskEither(
+ Contracts.getHeadersByRangeViaAxios(axiosInstance)(range)({
+ tags,
+ partyAddresses,
+ partyRoles,
+ })
+ );
+ }
+ ),
+ getContractById: withDynamicTypeCheck(
+ strict,
+ Contract.GetContractByIdRequest,
+ (request) => {
+ return Contract.getContractById(axiosInstance, request.contractId);
+ }
+ ),
+ buildCreateContractTx: withDynamicTypeCheck(
+ strict,
+ Contracts.BuildCreateContractTxRequestGuard,
+ async (request) => {
+ const version = await runtimeVersion;
+ // NOTE: Runtime 0.0.5 requires an explicit minUTxODeposit, but 0.0.6 and forward allows that field as optional
+ // and it will calculate the actual minimum required. We use the version of the runtime to determine
+ // if we use a "safe" default that is bigger than needed.
+ const minUTxODeposit =
+ request.minimumLovelaceUTxODeposit ??
+ (version === "0.0.5" ? 3000000 : undefined);
+ const postContractsRequest = {
+ contract: "contract" in request ? request.contract : request.sourceId,
+ version: request.version,
+ metadata: request.metadata ?? {},
+ tags: request.tags ?? {},
+ minUTxODeposit,
+ roles: request.roles,
+ threadRoleName: request.threadRoleName,
+ };
+ const addressesAndCollaterals = {
+ changeAddress: request.changeAddress,
+ usedAddresses: request.usedAddresses ?? [],
+ collateralUTxOs: request.collateralUTxOs ?? [],
+ };
+ return unsafeTaskEither(
+ Contracts.postViaAxios(axiosInstance)(
+ postContractsRequest,
+ addressesAndCollaterals,
+ request.stakeAddress
+ )
+ );
+ }
+ ),
+ createContractSources: withDynamicTypeCheck(
+ strict,
+ Sources.CreateContractSourcesRequestGuard,
+ (request) => {
+ const {
+ bundle: { main, bundle },
+ } = request;
+ return Sources.createContractSources(axiosInstance)(main, bundle);
+ }
+ ),
+ getContractSourceById: withDynamicTypeCheck(
+ strict,
+ Sources.GetContractBySourceIdRequestGuard,
+ (request) => {
+ return Sources.getContractSourceById(axiosInstance)(request);
+ }
+ ),
+ getContractSourceAdjacency: withDynamicTypeCheck(
+ strict,
+ Sources.GetContractSourceAdjacencyRequestGuard,
+ (request) => {
+ return Sources.getContractSourceAdjacency(axiosInstance)(request);
+ }
+ ),
+ getContractSourceClosure: withDynamicTypeCheck(
+ strict,
+ Sources.GetContractSourceClosureRequestGuard,
+ (request) => {
+ return Sources.getContractSourceClosure(axiosInstance)(request);
+ }
+ ),
+ getNextStepsForContract: withDynamicTypeCheck(
+ strict,
+ Next.GetNextStepsForContractRequestGuard,
+ (request) => {
+ return Next.getNextStepsForContract(axiosInstance)(request);
+ }
+ ),
+ submitContract: withDynamicTypeCheck(
+ strict,
+ Contract.SubmitContractRequestGuard,
+ (request) => {
+ const { contractId, txEnvelope } = request;
+ return Contract.submitContract(axiosInstance)(contractId, txEnvelope);
+ }
+ ),
+ getTransactionsForContract: withDynamicTypeCheck(
+ strict,
+ Transactions.GetTransactionsForContractRequestGuard,
+ (request) => {
+ const { contractId, range } = request;
+ return unsafeTaskEither(
+ Transactions.getHeadersByRangeViaAxios(axiosInstance)(
+ contractId,
+ range
+ )
+ );
+ }
+ ),
+ submitContractTransaction: withDynamicTypeCheck(
+ strict,
+ Transaction.SubmitContractTransactionRequestGuard,
+ (request) => {
+ const { contractId, transactionId, hexTransactionWitnessSet } = request;
+ return unsafeTaskEither(
+ Transaction.putViaAxios(axiosInstance)(
+ contractId,
+ transactionId,
+ hexTransactionWitnessSet
+ )
+ );
+ }
+ ),
+ getContractTransactionById: withDynamicTypeCheck(
+ strict,
+ Transaction.GetContractTransactionByIdRequestGuard,
+ (request) => {
+ const { contractId, txId } = request;
+ return unsafeTaskEither(
+ Transaction.getViaAxios(axiosInstance)(contractId, txId)
+ );
+ }
+ ),
+ withdrawPayouts: withDynamicTypeCheck(
+ strict,
+ Withdrawals.WithdrawPayoutsRequestGuard,
+ (request) => {
+ const { payoutIds, changeAddress } = request;
+ return unsafeTaskEither(
+ Withdrawals.postViaAxios(axiosInstance)(payoutIds, {
+ changeAddress,
usedAddresses: request.usedAddresses ?? [],
collateralUTxOs: request.collateralUTxOs ?? [],
- };
- return unsafeTaskEither(
- Contracts.postViaAxios(axiosInstance)(postContractsRequest, addressesAndCollaterals, request.stakeAddress)
- );
- }
- );
- },
- createContractSources(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Sources.CreateContractSourcesRequestGuard.decode(x),
- strict,
- (request) => {
- const {
- bundle: { main, bundle },
- } = request;
- return Sources.createContractSources(axiosInstance)(main, bundle);
- }
- );
- },
- getContractSourceById(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Sources.GetContractBySourceIdRequestGuard.decode(x),
- strict,
- (request) => {
- return Sources.getContractSourceById(axiosInstance)(request);
- }
- );
- },
- getContractSourceAdjacency(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Sources.GetContractSourceAdjacencyRequestGuard.decode(x),
- strict,
- (request) => {
- return Sources.getContractSourceAdjacency(axiosInstance)(request);
- }
- );
- },
- getContractSourceClosure(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Sources.GetContractSourceClosureRequestGuard.decode(x),
- strict,
- (request) => {
- return Sources.getContractSourceClosure(axiosInstance)(request);
- }
- );
- },
- getNextStepsForContract(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Next.GetNextStepsForContractRequestGuard.decode(x),
- strict,
- (request) => {
- return Next.getNextStepsForContract(axiosInstance)(request);
- }
- );
- },
- submitContract(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Contract.SubmitContractRequestGuard.decode(x),
- strict,
- (request) => {
- const { contractId, txEnvelope } = request;
- return Contract.submitContract(axiosInstance)(contractId, txEnvelope);
- }
- );
- },
- getTransactionsForContract(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Transactions.GetTransactionsForContractRequestGuard.decode(x),
- strict,
- (request) => {
- const { contractId, range } = request;
- return unsafeTaskEither(Transactions.getHeadersByRangeViaAxios(axiosInstance)(contractId, range));
- }
- );
- },
- submitContractTransaction(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Transaction.SubmitContractTransactionRequestGuard.decode(x),
- strict,
- (request) => {
- const { contractId, transactionId, hexTransactionWitnessSet } = request;
- return unsafeTaskEither(
- Transaction.putViaAxios(axiosInstance)(contractId, transactionId, hexTransactionWitnessSet)
- );
- }
- );
- },
- getContractTransactionById(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Transaction.GetContractTransactionByIdRequestGuard.decode(x),
- strict,
- (request) => {
- const { contractId, txId } = request;
- return unsafeTaskEither(Transaction.getViaAxios(axiosInstance)(contractId, txId));
- }
- );
- },
- withdrawPayouts(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Withdrawals.WithdrawPayoutsRequestGuard.decode(x),
- strict,
- (request) => {
- const { payoutIds, changeAddress } = request;
- return unsafeTaskEither(
- Withdrawals.postViaAxios(axiosInstance)(payoutIds, {
+ })
+ );
+ }
+ ),
+ getWithdrawalById: withDynamicTypeCheck(
+ strict,
+ Withdrawal.GetWithdrawalByIdRequestGuard,
+ async (request) => {
+ const { withdrawalId } = request;
+ const { block, ...response } = await unsafeTaskEither(
+ Withdrawal.getViaAxios(axiosInstance)(withdrawalId)
+ );
+ return { ...response, block: O.toUndefined(block) };
+ }
+ ),
+ getWithdrawals: withDynamicTypeCheck(
+ strict,
+ Withdrawals.GetWithdrawalsRequestGuard,
+ (request) => {
+ return unsafeTaskEither(
+ Withdrawals.getHeadersByRangeViaAxios(axiosInstance)(request)
+ );
+ }
+ ),
+ applyInputsToContract: withDynamicTypeCheck(
+ strict,
+ Transactions.ApplyInputsToContractRequestGuard,
+ (request) => {
+ const {
+ contractId,
+ changeAddress,
+ invalidBefore,
+ invalidHereafter,
+ inputs,
+ } = request;
+ return unsafeTaskEither(
+ Transactions.postViaAxios(axiosInstance)(
+ contractId,
+ {
+ invalidBefore,
+ invalidHereafter,
+ version: request.version ?? "v1",
+ metadata: request.metadata ?? {},
+ tags: request.tags ?? {},
+ inputs,
+ },
+ {
changeAddress,
usedAddresses: request.usedAddresses ?? [],
collateralUTxOs: request.collateralUTxOs ?? [],
- })
- );
- }
- );
- },
- async getWithdrawalById(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Withdrawal.GetWithdrawalByIdRequestGuard.decode(x),
- strict,
- async (request) => {
- const { withdrawalId } = request;
- const { block, ...response } = await unsafeTaskEither(Withdrawal.getViaAxios(axiosInstance)(withdrawalId));
- return { ...response, block: O.toUndefined(block) };
- }
- );
- },
- getWithdrawals(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Withdrawals.GetWithdrawalsRequestGuard.decode(x),
- strict,
- (request) => {
- return unsafeTaskEither(Withdrawals.getHeadersByRangeViaAxios(axiosInstance)(request));
- }
- );
- },
- applyInputsToContract(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Transactions.ApplyInputsToContractRequestGuard.decode(x),
- strict,
- (request) => {
- const { contractId, changeAddress, invalidBefore, invalidHereafter, inputs } = request;
- return unsafeTaskEither(
- Transactions.postViaAxios(axiosInstance)(
- contractId,
- {
- invalidBefore,
- invalidHereafter,
- version: request.version ?? "v1",
- metadata: request.metadata ?? {},
- tags: request.tags ?? {},
- inputs,
- },
- {
- changeAddress,
- usedAddresses: request.usedAddresses ?? [],
- collateralUTxOs: request.collateralUTxOs ?? [],
- }
- )
- );
- }
- );
- },
- submitWithdrawal(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Withdrawal.SubmitWithdrawalRequestGuard.decode(x),
- strict,
- (request) => {
- const { withdrawalId, hexTransactionWitnessSet } = request;
- return unsafeTaskEither(Withdrawal.putViaAxios(axiosInstance)(withdrawalId, hexTransactionWitnessSet));
- }
- );
- },
- async getPayouts(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Payouts.GetPayoutsRequestGuard.decode(x),
- strict,
- async (request) => {
- const { contractIds, roleTokens, range, status } = request;
- return await unsafeTaskEither(
- Payouts.getHeadersByRangeViaAxios(axiosInstance)(range)(contractIds)(roleTokens)(O.fromNullable(status))
- );
- }
- );
- },
- async getPayoutById(request) {
- return withDynamicTypeCheck(
- request,
- (x) => Payout.GetPayoutByIdRequestGuard.decode(x),
- strict,
- async (request) => {
- const { payoutId } = request;
- const result = await unsafeTaskEither(Payout.getViaAxios(axiosInstance)(payoutId));
- return {
- payoutId: result.payoutId,
- contractId: result.contractId,
- ...O.match(
- () => ({}),
- (withdrawalId) => ({ withdrawalId })
- )(result.withdrawalId),
- role: result.role,
- payoutValidatorAddress: result.payoutValidatorAddress,
- status: result.status,
- assets: result.assets,
- };
- }
- );
- },
+ }
+ )
+ );
+ }
+ ),
+ submitWithdrawal: withDynamicTypeCheck(
+ strict,
+ Withdrawal.SubmitWithdrawalRequestGuard,
+ (request) => {
+ const { withdrawalId, hexTransactionWitnessSet } = request;
+ return unsafeTaskEither(
+ Withdrawal.putViaAxios(axiosInstance)(
+ withdrawalId,
+ hexTransactionWitnessSet
+ )
+ );
+ }
+ ),
+ getPayouts: withDynamicTypeCheck(
+ strict,
+ Payouts.GetPayoutsRequestGuard,
+ async (request) => {
+ const { contractIds, roleTokens, range, status } = request;
+ return await unsafeTaskEither(
+ Payouts.getHeadersByRangeViaAxios(axiosInstance)(range)(contractIds)(
+ roleTokens
+ )(O.fromNullable(status))
+ );
+ }
+ ),
+ getPayoutById: withDynamicTypeCheck(
+ strict,
+ Payout.GetPayoutByIdRequestGuard,
+ async (request) => {
+ const { payoutId } = request;
+ const result = await unsafeTaskEither(
+ Payout.getViaAxios(axiosInstance)(payoutId)
+ );
+ return {
+ payoutId: result.payoutId,
+ contractId: result.contractId,
+ ...O.match(
+ () => ({}),
+ (withdrawalId) => ({ withdrawalId })
+ )(result.withdrawalId),
+ role: result.role,
+ payoutValidatorAddress: result.payoutValidatorAddress,
+ status: result.status,
+ assets: result.assets,
+ };
+ }
+ ),
};
}
diff --git a/packages/runtime/lifecycle/src/browser/index.ts b/packages/runtime/lifecycle/src/browser/index.ts
index d343e928..457abc68 100644
--- a/packages/runtime/lifecycle/src/browser/index.ts
+++ b/packages/runtime/lifecycle/src/browser/index.ts
@@ -41,7 +41,7 @@ import * as Generic from "../generic/runtime.js";
import { mkFPTSRestClient, mkRestClient } from "@marlowe.io/runtime-rest-client";
import { RuntimeLifecycle } from "../api.js";
-import { InvalidTypeError, strictDynamicTypeCheck } from "@marlowe.io/adapter/io-ts";
+import { InvalidTypeError, dynamicAssertType } from "@marlowe.io/adapter/io-ts";
import * as t from "io-ts/lib/index.js";
/**
@@ -69,25 +69,6 @@ export const BrowserRuntimeLifecycleOptionsGuard: t.Type;
/**
* Creates an instance of RuntimeLifecycle using the browser wallet.
* @param options
@@ -98,12 +79,12 @@ export async function mkRuntimeLifecycle(
options: BrowserRuntimeLifecycleOptions,
strict = true
): Promise {
- if (!strictDynamicTypeCheck(strict)) {
- throw new InvalidTypeError([], `Invalid type for argument 'strict', expected boolean but got ${strict}`);
- }
- if (!mkRuntimeLifecycleArgumentDynamicTypeCheck(options, strict)) {
- throw new InvalidTypeError([], `Invalid type for argument 'options', expected string but got ${options}`);
- }
+ dynamicAssertType(BrowserRuntimeLifecycleOptionsGuard, options);
+ dynamicAssertType(
+ t.boolean,
+ strict,
+ "Invalid type for argument 'strict', expected boolean"
+ );
const { runtimeURL, walletName } = options;
const wallet = await mkBrowserWallet(walletName);
diff --git a/packages/runtime/lifecycle/src/index.ts b/packages/runtime/lifecycle/src/index.ts
index e3a04f7b..276741a8 100644
--- a/packages/runtime/lifecycle/src/index.ts
+++ b/packages/runtime/lifecycle/src/index.ts
@@ -20,8 +20,8 @@ import { WalletAPI } from "@marlowe.io/wallet";
import * as Generic from "./generic/runtime.js";
import { mkFPTSRestClient, mkRestClient } from "@marlowe.io/runtime-rest-client";
import { RuntimeLifecycle } from "./api.js";
-import { InvalidTypeError, strictDynamicTypeCheck } from "@marlowe.io/adapter/io-ts";
-
+import { dynamicAssertType } from "@marlowe.io/adapter/io-ts";
+import * as t from "io-ts/lib/index.js";
export * as Browser from "./browser/index.js";
/**
@@ -40,21 +40,31 @@ export interface RuntimeLifecycleOptions {
}
/**
- * Creates an instance of RuntimeLifecycle.
- * @param options
- * @category RuntimeLifecycle
+ * @hidden
*/
-export function mkRuntimeLifecycle(options: RuntimeLifecycleOptions): RuntimeLifecycle;
+export const RuntimeLifecycleOptionsGuard: t.Type =
+ t.type({
+ runtimeURL: t.string,
+ // TODO: Create a shallow guard for the wallet that checks that all methods are present as t.function.
+ wallet: t.any,
+ });
+
/**
* Creates an instance of RuntimeLifecycle.
* @param options
* @param strict Whether to perform runtime checking to provide helpful error messages. May have a slight negative performance impact. Default value is `true`.
* @category RuntimeLifecycle
*/
-export function mkRuntimeLifecycle(options: RuntimeLifecycleOptions, strict = true): RuntimeLifecycle {
- if (!strictDynamicTypeCheck(strict)) {
- throw new InvalidTypeError([], `Invalid type for argument 'strict', expected boolean but got ${strict}`);
- }
+export function mkRuntimeLifecycle(
+ options: RuntimeLifecycleOptions,
+ strict = true
+): RuntimeLifecycle {
+ dynamicAssertType(RuntimeLifecycleOptionsGuard, options);
+ dynamicAssertType(
+ t.boolean,
+ strict,
+ "Invalid type for argument 'strict', expected boolean"
+ );
const { runtimeURL, wallet } = options;
const deprecatedRestAPI = mkFPTSRestClient(runtimeURL);
const restClient = mkRestClient(runtimeURL, strict);
diff --git a/packages/runtime/lifecycle/src/nodejs/index.ts b/packages/runtime/lifecycle/src/nodejs/index.ts
index 23403d52..40c4fb23 100644
--- a/packages/runtime/lifecycle/src/nodejs/index.ts
+++ b/packages/runtime/lifecycle/src/nodejs/index.ts
@@ -3,14 +3,19 @@ import * as Wallet from "@marlowe.io/wallet/lucid";
import * as Generic from "../generic/runtime.js";
import { RuntimeLifecycle } from "../api.js";
import { Lucid } from "lucid-cardano";
-import { InvalidTypeError, strictDynamicTypeCheck } from "@marlowe.io/adapter/io-ts";
+import * as t from "io-ts/lib/index.js";
+import { dynamicAssertType } from "@marlowe.io/adapter/io-ts";
-export async function mkRuntimeLifecycle(runtimeURL: string, lucid: Lucid): Promise;
-export async function mkRuntimeLifecycle(runtimeURL: string, lucid: Lucid, strict: boolean): Promise;
-export async function mkRuntimeLifecycle(runtimeURL: string, lucid: Lucid, strict: unknown = true) {
- if (!strictDynamicTypeCheck(strict)) {
- throw new InvalidTypeError([], `Invalid type for argument 'strict', expected boolean but got ${strict}`);
- }
+export async function mkRuntimeLifecycle(
+ runtimeURL: string,
+ lucid: Lucid,
+ strict = true
+): Promise {
+ dynamicAssertType(
+ t.boolean,
+ strict,
+ "Invalid type for argument 'strict', expected boolean"
+ );
const wallet = await Wallet.mkLucidWallet(lucid);
const deprecatedRestAPI = mkFPTSRestClient(runtimeURL);
const restClient = mkRestClient(runtimeURL, strict);
From dd247396d7848cac1a4bf074a64a5c5f149950b1 Mon Sep 17 00:00:00 2001
From: Nicolas Henin
Date: Thu, 18 Apr 2024 15:53:34 +0200
Subject: [PATCH 10/14] Fixed Documentation warnings
---
packages/runtime/client/rest/src/index.ts | 5 ++++
packages/runtime/lifecycle/src/api.ts | 26 ++++++++++++++++++-
.../src/generic/applicable-actions.ts | 2 +-
.../lifecycle/src/generic/contracts.ts | 8 +-----
.../lifecycle/src/generic/new-contract-api.ts | 2 +-
5 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts
index fc4922b4..2a99b80b 100644
--- a/packages/runtime/client/rest/src/index.ts
+++ b/packages/runtime/client/rest/src/index.ts
@@ -609,6 +609,11 @@ export interface ContractsAPI {
*/
export type RestDI = { restClient: RestClient };
+/**
+ *
+ * @description Dependency Injection for the Wallet API
+ * @hidden
+ */
export type DeprecatedRestDI = { deprecatedRestAPI: FPTSRestAPI };
/**
diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts
index 93ad387a..88b810a0 100644
--- a/packages/runtime/lifecycle/src/api.ts
+++ b/packages/runtime/lifecycle/src/api.ts
@@ -31,9 +31,20 @@ import {
ContractInstanceAPI,
} from "./generic/new-contract-api.js";
import {
+ ApplyInputsRequest,
ContractsAPI,
+ CreateContractRequest,
CreateContractRequestBase,
+ CreateContractRequestFromBundle,
+ CreateContractRequestFromContract,
} from "./generic/contracts.js";
+import {
+ ContractsAPI as NewContractsAPI,
+ ApplicableActionsAPI as NewApplicableActionsAPI,
+ ComputeApplicableActionsRequest,
+} from "./generic/new-contract-api.js";
+import * as NewContract from "./generic/new-contract-api.js";
+
export {
ApplicableActionsAPI,
ApplicableAction,
@@ -50,8 +61,15 @@ export {
ContractDetails,
ContractInstanceAPI,
CreateContractRequestBase,
+ ContractsAPI,
+ ApplyInputsRequest,
+ CreateContractRequest,
+ NewContractsAPI,
+ NewApplicableActionsAPI,
+ ComputeApplicableActionsRequest,
+ CreateContractRequestFromContract,
+ CreateContractRequestFromBundle,
};
-import * as NewContract from "./generic/new-contract-api.js";
/**
* This is the main entry point of the @marlowe.io/runtime-lifecycle package. It provides a set of APIs to
@@ -70,9 +88,15 @@ export interface RuntimeLifecycle {
* Access to the low-level REST API as defined in the {@link @marlowe.io/runtime-rest-client! } package. It is re-exported here for convenience.
*/
restClient: RestClient;
+
+ /**
+ * The new contract API is a high level API that lets you create and interact with Marlowe contracts.
+ */
newContractAPI: NewContract.ContractsAPI;
+
/**
* The contracts API is a high level API that lets you create and interact with Marlowe contracts.
+ * @deprecated Use {@link RuntimeLifecycle.newContractAPI} instead.
*/
contracts: ContractsAPI;
payouts: PayoutsAPI;
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index 26cd1a43..0e0e2601 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -40,7 +40,7 @@ import {
Tags,
TxId,
} from "@marlowe.io/runtime-core";
-import { RestClient, RestDI, Tip } from "@marlowe.io/runtime-rest-client";
+import { RestDI } from "@marlowe.io/runtime-rest-client";
import { WalletAPI, WalletDI } from "@marlowe.io/wallet";
import * as Big from "@marlowe.io/adapter/bigint";
import { ContractSourceId } from "@marlowe.io/marlowe-object";
diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts
index c4f55dcf..fe437486 100644
--- a/packages/runtime/lifecycle/src/generic/contracts.ts
+++ b/packages/runtime/lifecycle/src/generic/contracts.ts
@@ -1,5 +1,3 @@
-import * as TE from "fp-ts/lib/TaskEither.js";
-import { pipe } from "fp-ts/lib/function.js";
import { Option } from "fp-ts/lib/Option.js";
import {
Contract,
@@ -8,7 +6,7 @@ import {
Party,
RoleName,
} from "@marlowe.io/language-core-v1";
-import { tryCatchDefault, unsafeTaskEither } from "@marlowe.io/adapter/fp-ts";
+import { unsafeTaskEither } from "@marlowe.io/adapter/fp-ts";
import {
getAddressesAndCollaterals,
@@ -20,8 +18,6 @@ import {
ContractId,
contractIdToTxId,
TxId,
- AddressesAndCollaterals,
- HexTransactionWitnessSet,
transactionWitnessSetTextEnvelope,
BlockHeader,
StakeAddressBech32,
@@ -36,14 +32,12 @@ import {
ItemRange,
DeprecatedRestDI,
} from "@marlowe.io/runtime-rest-client";
-import { DecodingError } from "@marlowe.io/adapter/codec";
import { Next, noNext } from "@marlowe.io/language-core-v1/next";
import {
BuildCreateContractTxRequest,
BuildCreateContractTxRequestOptions,
RolesConfiguration,
- TransactionTextEnvelope,
} from "@marlowe.io/runtime-rest-client/contract";
import { SingleInputTx } from "@marlowe.io/language-core-v1/transaction.js";
import { ISO8601, iso8601ToPosixTime } from "@marlowe.io/adapter/time";
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index d98ee9a0..a87eaf69 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -140,7 +140,7 @@ function mkApplicableActionsAPI(
* TODO comment
* @category New ContractsAPI
*/
-type ComputeApplicableActionsRequest = {
+export type ComputeApplicableActionsRequest = {
environment?: Environment;
contractDetails?: ContractDetails;
};
From 0837b007f2d60f18169b4935d2384781ad739934 Mon Sep 17 00:00:00 2001
From: Nicolas Henin
Date: Sat, 20 Apr 2024 09:59:22 +0200
Subject: [PATCH 11/14] refined NewContractAPI interface
---
examples/nodejs/src/escrow-flow.ts | 4 +-
.../src/experimental-features/source-map.ts | 19 +-
examples/nodejs/src/marlowe-object-flow.ts | 30 +--
jsdelivr-npm-importmap.js | 8 +-
packages/language/core/v1/src/environment.ts | 8 +-
packages/language/examples/src/atomicSwap.ts | 80 +++---
.../language/examples/test/atomicSwap.spec.ts | 42 ++--
packages/runtime/lifecycle/src/api.ts | 25 +-
.../src/generic/applicable-actions.ts | 58 ++---
.../lifecycle/src/generic/contracts.ts | 123 +++-------
.../lifecycle/src/generic/new-contract-api.ts | 230 +++++++++++-------
.../test/examples/swap.ada.token.e2e.spec.ts | 116 +++------
.../test/generic/contracts.e2e.spec.ts | 32 +--
13 files changed, 325 insertions(+), 450 deletions(-)
diff --git a/examples/nodejs/src/escrow-flow.ts b/examples/nodejs/src/escrow-flow.ts
index 09bbc2c7..0b8f814a 100644
--- a/examples/nodejs/src/escrow-flow.ts
+++ b/examples/nodejs/src/escrow-flow.ts
@@ -88,12 +88,12 @@ async function main(action: "buy" | "sell", otherAddress: string, amount: number
console.log("Mediator: " + Mediator);
console.log("Amount: " + amount);
- const contractInstance = await runtime.newContractAPI.createContract({
+ const contractInstance = await runtime.newContractAPI.create({
contract: escrow,
roles: { Buyer, Seller, Mediator },
});
- console.log("Contract ID: " + contractInstance.contractId);
+ console.log("Contract ID: " + contractInstance.id);
console.log("Waiting for confirmation...");
await contractInstance.waitForConfirmation();
diff --git a/examples/nodejs/src/experimental-features/source-map.ts b/examples/nodejs/src/experimental-features/source-map.ts
index b2c56628..076b61d9 100644
--- a/examples/nodejs/src/experimental-features/source-map.ts
+++ b/examples/nodejs/src/experimental-features/source-map.ts
@@ -1,16 +1,7 @@
import * as M from "fp-ts/lib/Map.js";
-import {
- ContractBundleMap,
- bundleMapToList,
- isAnnotated,
- stripAnnotations,
-} from "@marlowe.io/marlowe-object";
-import {
- ContractInstanceAPI,
- CreateContractRequestBase,
- RuntimeLifecycle,
-} from "@marlowe.io/runtime-lifecycle/api";
+import { ContractBundleMap, bundleMapToList, isAnnotated, stripAnnotations } from "@marlowe.io/marlowe-object";
+import { ContractInstanceAPI, CreateContractRequestBase, RuntimeLifecycle } from "@marlowe.io/runtime-lifecycle/api";
import { ContractClosure, getContractClosure } from "./contract-closure.js";
import * as Core from "@marlowe.io/language-core-v1";
@@ -163,9 +154,7 @@ export interface SourceMap {
closure: ContractClosure;
annotateHistory(history: SingleInputTx[]): SingleInputTx[];
playHistory(history: SingleInputTx[]): TransactionOutput;
- createContract(
- options: CreateContractRequestBase
- ): Promise;
+ createContract(options: CreateContractRequestBase): Promise;
contractInstanceOf(contractId: ContractId): Promise;
}
@@ -188,7 +177,7 @@ export async function mkSourceMap(
},
createContract: (options: CreateContractRequestBase) => {
const contract = stripAnnotations(closure.contracts.get(closure.main)!);
- return lifecycle.newContractAPI.createContract({
+ return lifecycle.newContractAPI.create({
...options,
contract,
});
diff --git a/examples/nodejs/src/marlowe-object-flow.ts b/examples/nodejs/src/marlowe-object-flow.ts
index 9c5c4202..968dcca5 100644
--- a/examples/nodejs/src/marlowe-object-flow.ts
+++ b/examples/nodejs/src/marlowe-object-flow.ts
@@ -181,10 +181,7 @@ async function createContractMenu(lifecycle: RuntimeLifecycle, rewardAddress?: S
console.log(`Contract created with id ${contractId}`);
- await waitIndicator(
- lifecycle.wallet,
- contractIdToTxId(contractInstance.contractId)
- );
+ await waitIndicator(lifecycle.wallet, contractIdToTxId(contractInstance.id));
return contractMenu(lifecycle.wallet, contractInstance, scheme, sourceMap);
}
@@ -216,19 +213,10 @@ async function loadContractMenu(lifecycle: RuntimeLifecycle) {
console.log(` * Pay from: ${validationResult.scheme.payer}`);
console.log(` * Pay to: ${validationResult.scheme.payee}`);
console.log(` * Amount: ${validationResult.scheme.amount} lovelaces`);
- console.log(
- ` * Deposit deadline: ${validationResult.scheme.depositDeadline}`
- );
- console.log(
- ` * Release deadline: ${validationResult.scheme.releaseDeadline}`
- );
- const contractInstance = await lifecycle.newContractAPI.loadContract(cid);
- return contractMenu(
- lifecycle.wallet,
- contractInstance,
- validationResult.scheme,
- validationResult.sourceMap
- );
+ console.log(` * Deposit deadline: ${validationResult.scheme.depositDeadline}`);
+ console.log(` * Release deadline: ${validationResult.scheme.releaseDeadline}`);
+ const contractInstance = await lifecycle.newContractAPI.load(cid);
+ return contractMenu(lifecycle.wallet, contractInstance, validationResult.scheme, validationResult.sourceMap);
}
/**
@@ -243,16 +231,12 @@ async function contractMenu(
// Get and print the contract logical state.
const inputHistory = await contractInstance.getInputHistory();
- const contractState = getState(
- datetoTimeout(new Date()),
- inputHistory,
- sourceMap
- );
+ const contractState = getState(datetoTimeout(new Date()), inputHistory, sourceMap);
if (contractState.type === "Closed") return;
printState(contractState, scheme);
// See what actions are applicable to the current contract state
- const applicableActions = await contractInstance.computeApplicableActions();
+ const applicableActions = await contractInstance.evaluateApplicableActions();
const choices: Array<{
name: string;
diff --git a/jsdelivr-npm-importmap.js b/jsdelivr-npm-importmap.js
index a357d258..e6b7b05a 100644
--- a/jsdelivr-npm-importmap.js
+++ b/jsdelivr-npm-importmap.js
@@ -38,12 +38,16 @@ const importMap = {
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.4.0-beta-rc1/dist/bundled/esm/version.js",
"@marlowe.io/language-examples":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.4.0-beta-rc1/dist/bundled/esm/language-examples.js",
+ "@marlowe.io/language-examples/atomic-swap":
+ "https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.4.0-beta-rc1/dist/bundled/esm/atomic-swap.js",
"@marlowe.io/language-specification-client":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-specification-client@0.4.0-beta-rc1/dist/bundled/esm/language-specification-client.js",
"@marlowe.io/token-metadata-client":
"https://cdn.jsdelivr.net/npm/@marlowe.io/token-metadata-client@0.4.0-beta-rc1/dist/bundled/esm/token-metadata-client.js",
- "@marlowe.io/wallet": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/wallet.js",
- "@marlowe.io/wallet/api": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/api.js",
+ "@marlowe.io/wallet":
+ "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/wallet.js",
+ "@marlowe.io/wallet/api":
+ "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/api.js",
"@marlowe.io/wallet/browser":
"https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/browser.js",
"@marlowe.io/wallet/lucid":
diff --git a/packages/language/core/v1/src/environment.ts b/packages/language/core/v1/src/environment.ts
index 17c82b48..04783146 100644
--- a/packages/language/core/v1/src/environment.ts
+++ b/packages/language/core/v1/src/environment.ts
@@ -12,7 +12,7 @@ export const mkEnvironment =
});
/**
- * TODO: Comment
+ * Time interval in which the contract is executed. It is defined by a start and end time. The time is represented as a POSIX time.
* @see Appendix E.16 of the {@link https://github.com/input-output-hk/marlowe/releases/download/v3/Marlowe.pdf | Marlowe specification}
* @category Environment
*/
@@ -22,7 +22,7 @@ export interface TimeInterval {
}
/**
- * TODO: Comment
+ * Guard for {@link TimeInterval}
* @see Appendix E.16 of the {@link https://github.com/input-output-hk/marlowe/releases/download/v3/Marlowe.pdf | Marlowe specification}
* @category Environment
*/
@@ -32,7 +32,7 @@ export const TimeIntervalGuard: t.Type = t.type({
});
/**
- * TODO: Comment
+ * Time interval in which the contract is executed.
* @see Section 2.1.10 and appendix E.22 of the {@link https://github.com/input-output-hk/marlowe/releases/download/v3/Marlowe.pdf | Marlowe specification}
* @category Environment
*/
@@ -41,7 +41,7 @@ export interface Environment {
}
/**
- * TODO: Comment
+ * Guard for {@link Environment}
* @see Section 2.1.10 and appendix E.22 of the {@link https://github.com/input-output-hk/marlowe/releases/download/v3/Marlowe.pdf | Marlowe specification}
* @category Environment
*/
diff --git a/packages/language/examples/src/atomicSwap.ts b/packages/language/examples/src/atomicSwap.ts
index caf977b2..ae76e31a 100644
--- a/packages/language/examples/src/atomicSwap.ts
+++ b/packages/language/examples/src/atomicSwap.ts
@@ -99,26 +99,26 @@ export type State = ActiveState | Closed;
export type ActiveState = WaitingSellerOffer | NoSellerOfferInTime | WaitingForAnswer | WaitingForSwapConfirmation;
export type WaitingSellerOffer = {
- typeName: "WaitingSellerOffer";
+ type: "WaitingSellerOffer";
};
export const waitingSellerOffer: WaitingSellerOffer = {
- typeName: "WaitingSellerOffer",
+ type: "WaitingSellerOffer",
};
export type NoSellerOfferInTime = {
- typeName: "NoSellerOfferInTime";
+ type: "NoSellerOfferInTime";
};
export const noSellerOfferInTime: NoSellerOfferInTime = {
- typeName: "NoSellerOfferInTime",
+ type: "NoSellerOfferInTime",
};
export type WaitingForAnswer = {
- typeName: "WaitingForAnswer";
+ type: "WaitingForAnswer";
};
export const waitingForAnswer: WaitingForAnswer = {
- typeName: "WaitingForAnswer",
+ type: "WaitingForAnswer",
};
/*
@@ -134,18 +134,18 @@ export const waitingForAnswer: WaitingForAnswer = {
*
*/
export type WaitingForSwapConfirmation = {
- typeName: "WaitingForSwapConfirmation";
+ type: "WaitingForSwapConfirmation";
};
export const waitingForSwapConfirmation: WaitingForSwapConfirmation = {
- typeName: "WaitingForSwapConfirmation",
+ type: "WaitingForSwapConfirmation",
};
/**
* when the contract is closed.
*/
export type Closed = {
- typeName: "Closed";
+ type: "Closed";
reason: CloseReason;
};
@@ -153,7 +153,7 @@ export type Closed = {
/**
* Action List available for the contract lifecycle.
*/
-export type Action =
+export type ApplicableAction =
/* When Contract Created (timed out > NoOfferProvisionnedOnTime) */
| ProvisionOffer // > OfferProvisionned
/* When NoOfferProvisionnedOnTime (timed out > no timeout (need to be reduced to be closed))*/
@@ -167,30 +167,30 @@ export type Action =
export type ActionParticipant = "buyer" | "seller" | "anybody";
export type RetrieveMinimumLovelaceAdded = {
- typeName: "RetrieveMinimumLovelaceAdded";
+ type: "RetrieveMinimumLovelaceAdded";
owner: ActionParticipant;
};
export type ProvisionOffer = {
- typeName: "ProvisionOffer";
+ type: "ProvisionOffer";
owner: ActionParticipant;
input: IDeposit;
};
export type Swap = {
- typeName: "Swap";
+ type: "Swap";
owner: ActionParticipant;
input: IDeposit;
};
export type ConfirmSwap = {
- typeName: "ConfirmSwap";
+ type: "ConfirmSwap";
owner: ActionParticipant;
input: INotify;
};
export type Retract = {
- typeName: "Retract";
+ type: "Retract";
owner: ActionParticipant;
input: IChoice;
};
@@ -206,14 +206,14 @@ export type CloseReason =
| SwappedButNotNotifiedOnTime;
export type NoOfferProvisionnedOnTime = {
- typeName: "NoOfferProvisionnedOnTime";
+ type: "NoOfferProvisionnedOnTime";
};
-export type SellerRetracted = { typeName: "SellerRetracted" };
-export type NotAnsweredOnTime = { typeName: "NotAnsweredOnTime" };
+export type SellerRetracted = { type: "SellerRetracted" };
+export type NotAnsweredOnTime = { type: "NotAnsweredOnTime" };
export type SwappedButNotNotifiedOnTime = {
- typeName: "SwappedButNotNotifiedOnTime";
+ type: "SwappedButNotNotifiedOnTime";
};
-export type Swapped = { typeName: "Swapped" };
+export type Swapped = { type: "Swapped" };
/* #endregion */
@@ -241,12 +241,12 @@ export class UnexpectedClosedSwapContractState extends Error {
}
}
-export const getAvailableActions = (scheme: Scheme, state: ActiveState): Action[] => {
- switch (state.typeName) {
+export const getApplicableActions = (scheme: Scheme, state: ActiveState): ApplicableAction[] => {
+ switch (state.type) {
case "WaitingSellerOffer":
return [
{
- typeName: "ProvisionOffer",
+ type: "ProvisionOffer",
owner: "seller",
input: {
input_from_party: scheme.offer.seller,
@@ -259,14 +259,14 @@ export const getAvailableActions = (scheme: Scheme, state: ActiveState): Action[
case "NoSellerOfferInTime":
return [
{
- typeName: "RetrieveMinimumLovelaceAdded",
+ type: "RetrieveMinimumLovelaceAdded",
owner: "anybody",
},
];
case "WaitingForAnswer":
return [
{
- typeName: "Swap",
+ type: "Swap",
owner: "buyer",
input: {
input_from_party: scheme.ask.buyer,
@@ -276,7 +276,7 @@ export const getAvailableActions = (scheme: Scheme, state: ActiveState): Action[
},
},
{
- typeName: "Retract",
+ type: "Retract",
owner: "seller",
input: {
for_choice_id: {
@@ -290,7 +290,7 @@ export const getAvailableActions = (scheme: Scheme, state: ActiveState): Action[
case "WaitingForSwapConfirmation":
return [
{
- typeName: "ConfirmSwap",
+ type: "ConfirmSwap",
owner: "anybody",
input: "input_notify",
},
@@ -307,13 +307,13 @@ export const getClosedState = (scheme: Scheme, inputHistory: SingleInputTx[]): C
// Offer Provision Deadline has passed and there is one reduced applied to close the contract
case 0:
return {
- typeName: "Closed",
- reason: { typeName: "NoOfferProvisionnedOnTime" },
+ type: "Closed",
+ reason: { type: "NoOfferProvisionnedOnTime" },
};
case 1:
return {
- typeName: "Closed",
- reason: { typeName: "NotAnsweredOnTime" },
+ type: "Closed",
+ reason: { type: "NotAnsweredOnTime" },
};
case 2: {
const isRetracted =
@@ -321,14 +321,14 @@ export const getClosedState = (scheme: Scheme, inputHistory: SingleInputTx[]): C
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
if (isRetracted && nbDeposits === 1) {
return {
- typeName: "Closed",
- reason: { typeName: "SellerRetracted" },
+ type: "Closed",
+ reason: { type: "SellerRetracted" },
};
}
if (nbDeposits === 2) {
return {
- typeName: "Closed",
- reason: { typeName: "SwappedButNotNotifiedOnTime" },
+ type: "Closed",
+ reason: { type: "SwappedButNotNotifiedOnTime" },
};
}
break;
@@ -338,8 +338,8 @@ export const getClosedState = (scheme: Scheme, inputHistory: SingleInputTx[]): C
const nbNotify = inputHistory.filter((singleInputTx) => G.INotify.is(singleInputTx.input)).length;
if (nbDeposits === 2 && nbNotify === 1) {
return {
- typeName: "Closed",
- reason: { typeName: "Swapped" },
+ type: "Closed",
+ reason: { type: "Swapped" },
};
}
}
@@ -355,16 +355,16 @@ export const getActiveState = (
): ActiveState => {
switch (inputHistory.length) {
case 0:
- return now < scheme.offer.deadline ? { typeName: "WaitingSellerOffer" } : { typeName: "NoSellerOfferInTime" };
+ return now < scheme.offer.deadline ? { type: "WaitingSellerOffer" } : { type: "NoSellerOfferInTime" };
case 1:
if (now < scheme.ask.deadline) {
- return { typeName: "WaitingForAnswer" };
+ return { type: "WaitingForAnswer" };
}
break;
case 2: {
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
if (nbDeposits === 2 && now < scheme.swapConfirmation.deadline) {
- return { typeName: "WaitingForSwapConfirmation" };
+ return { type: "WaitingForSwapConfirmation" };
}
break;
}
diff --git a/packages/language/examples/test/atomicSwap.spec.ts b/packages/language/examples/test/atomicSwap.spec.ts
index f052bc67..c8692d3e 100644
--- a/packages/language/examples/test/atomicSwap.spec.ts
+++ b/packages/language/examples/test/atomicSwap.spec.ts
@@ -10,7 +10,7 @@ import {
Retract,
Swap,
getActiveState,
- getAvailableActions,
+ getApplicableActions,
getClosedState,
waitingForAnswer,
waitingForSwapConfirmation,
@@ -71,7 +71,7 @@ describe("Atomic Swap", () => {
inputFlow.map((input) => ({ interval: aTxInterval, input: input })),
state
);
- expect(activeState.typeName).toBe("WaitingSellerOffer");
+ expect(activeState.type).toBe("WaitingSellerOffer");
});
it("when no seller offer have been provided in time - NoSellerOfferInTime", () => {
// Set up
@@ -105,7 +105,7 @@ describe("Atomic Swap", () => {
inputFlow.map((input) => ({ interval: aTxInterval, input: input })),
state
);
- expect(activeState.typeName).toBe("NoSellerOfferInTime");
+ expect(activeState.type).toBe("NoSellerOfferInTime");
});
it("when waiting a for an answer - WaitingForAnswer", () => {
// Set up
@@ -124,7 +124,7 @@ describe("Atomic Swap", () => {
deadline: aDeadlineInTheFuture,
},
};
- const inputFlow: Input[] = [(getAvailableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input];
+ const inputFlow: Input[] = [(getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input];
// Execute
@@ -150,7 +150,7 @@ describe("Atomic Swap", () => {
inputFlow.map((input) => ({ interval: aTxInterval, input: input })),
state
);
- expect(activeState.typeName).toBe("WaitingForAnswer");
+ expect(activeState.type).toBe("WaitingForAnswer");
});
it("when waiting a for a swap confirmation (Open Role requirement to prevent double-satisfaction attacks) - WaitingForSwapConfirmation", () => {
// Set up
@@ -170,8 +170,8 @@ describe("Atomic Swap", () => {
},
};
const inputFlow: Input[] = [
- (getAvailableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
- (getAvailableActions(scheme, waitingForAnswer)[0] as Swap).input,
+ (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
+ (getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input,
];
// Execute
@@ -211,7 +211,7 @@ describe("Atomic Swap", () => {
inputFlow.map((input) => ({ interval: aTxInterval, input: input })),
state
);
- expect(activeState.typeName).toBe("WaitingForSwapConfirmation");
+ expect(activeState.type).toBe("WaitingForSwapConfirmation");
});
});
describe("is closed (with 5 closed reasons)", () => {
@@ -233,9 +233,9 @@ describe("Atomic Swap", () => {
},
};
const inputFlow = [
- (getAvailableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
- (getAvailableActions(scheme, waitingForAnswer)[0] as Swap).input,
- (getAvailableActions(scheme, waitingForSwapConfirmation)[0] as ConfirmSwap).input,
+ (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
+ (getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input,
+ (getApplicableActions(scheme, waitingForSwapConfirmation)[0] as ConfirmSwap).input,
];
// Execute
@@ -286,7 +286,7 @@ describe("Atomic Swap", () => {
scheme,
inputFlow.map((input) => ({ interval: aTxInterval, input: input }))
);
- expect(state.reason.typeName).toBe("Swapped");
+ expect(state.reason.type).toBe("Swapped");
});
it("when tokens have been swapped but nobody has confirmed the swap on time (Open Role requirement to prevent double-satisfaction attacks) - SwappedButNotNotifiedOnTime", () => {
// Set up
@@ -306,8 +306,8 @@ describe("Atomic Swap", () => {
},
};
const inputFlow = [
- (getAvailableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
- (getAvailableActions(scheme, waitingForAnswer)[0] as Swap).input,
+ (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
+ (getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input,
];
// Execute
@@ -359,7 +359,7 @@ describe("Atomic Swap", () => {
scheme,
inputFlow.map((input) => ({ interval: aTxInterval, input: input }))
);
- expect(state.reason.typeName).toBe("SwappedButNotNotifiedOnTime");
+ expect(state.reason.type).toBe("SwappedButNotNotifiedOnTime");
});
it("when no buyer has answered to the offer on time - NotAnsweredOnTime", () => {
// Set up
@@ -378,7 +378,7 @@ describe("Atomic Swap", () => {
deadline: aDeadlineInTheFuture,
},
};
- const inputFlow = [(getAvailableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input];
+ const inputFlow = [(getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input];
// Execute
@@ -410,7 +410,7 @@ describe("Atomic Swap", () => {
scheme,
inputFlow.map((input) => ({ interval: aTxInterval, input: input }))
);
- expect(state.reason.typeName).toBe("NotAnsweredOnTime");
+ expect(state.reason.type).toBe("NotAnsweredOnTime");
});
it("when the seller has retracted - SellerRetracted", () => {
// Set up
@@ -430,8 +430,8 @@ describe("Atomic Swap", () => {
},
};
const inputFlow = [
- (getAvailableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
- (getAvailableActions(scheme, waitingForAnswer)[1] as Retract).input,
+ (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
+ (getApplicableActions(scheme, waitingForAnswer)[1] as Retract).input,
];
// Execute
@@ -464,7 +464,7 @@ describe("Atomic Swap", () => {
scheme,
inputFlow.map((input) => ({ interval: aTxInterval, input: input }))
);
- expect(state.reason.typeName).toBe("SellerRetracted");
+ expect(state.reason.type).toBe("SellerRetracted");
});
it("when the seller has not provisioned the contract on time and advance is applied - NoOfferProvisionnedOnTime", () => {
// Set up
@@ -507,7 +507,7 @@ describe("Atomic Swap", () => {
scheme,
advance.map((input) => ({ interval: aTxInterval, input: input }))
);
- expect(state.reason.typeName).toBe("NoOfferProvisionnedOnTime");
+ expect(state.reason.type).toBe("NoOfferProvisionnedOnTime");
});
});
});
diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts
index 88b810a0..7a24d89f 100644
--- a/packages/runtime/lifecycle/src/api.ts
+++ b/packages/runtime/lifecycle/src/api.ts
@@ -1,16 +1,6 @@
import { WalletAPI, WalletDI } from "@marlowe.io/wallet/api";
-import {
- AssetId,
- ContractId,
- PayoutAvailable,
- PayoutId,
- PayoutWithdrawn,
-} from "@marlowe.io/runtime-core";
-import {
- DeprecatedRestDI,
- RestClient,
- RestDI,
-} from "@marlowe.io/runtime-rest-client";
+import { AssetId, ContractId, PayoutAvailable, PayoutId, PayoutWithdrawn } from "@marlowe.io/runtime-core";
+import { DeprecatedRestDI, RestClient, RestDI } from "@marlowe.io/runtime-rest-client";
import {
ApplicableActionsAPI,
@@ -24,12 +14,7 @@ import {
CanDeposit,
CanNotify,
} from "./generic/applicable-actions.js";
-import {
- ActiveContract,
- ClosedContract,
- ContractDetails,
- ContractInstanceAPI,
-} from "./generic/new-contract-api.js";
+import { ActiveContract, ClosedContract, ContractDetails, ContractInstanceAPI } from "./generic/new-contract-api.js";
import {
ApplyInputsRequest,
ContractsAPI,
@@ -41,7 +26,7 @@ import {
import {
ContractsAPI as NewContractsAPI,
ApplicableActionsAPI as NewApplicableActionsAPI,
- ComputeApplicableActionsRequest,
+ EvaluateApplicableActionsRequest,
} from "./generic/new-contract-api.js";
import * as NewContract from "./generic/new-contract-api.js";
@@ -66,7 +51,7 @@ export {
CreateContractRequest,
NewContractsAPI,
NewApplicableActionsAPI,
- ComputeApplicableActionsRequest,
+ EvaluateApplicableActionsRequest as ComputeApplicableActionsRequest,
CreateContractRequestFromContract,
CreateContractRequestFromBundle,
};
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index 0e0e2601..f4b23900 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -32,14 +32,7 @@ import {
reduceContractUntilQuiescent,
TransactionSuccess,
} from "@marlowe.io/language-core-v1/semantics";
-import {
- AddressBech32,
- ContractId,
- Metadata,
- PolicyId,
- Tags,
- TxId,
-} from "@marlowe.io/runtime-core";
+import { AddressBech32, ContractId, Metadata, PolicyId, Tags, TxId } from "@marlowe.io/runtime-core";
import { RestDI } from "@marlowe.io/runtime-rest-client";
import { WalletAPI, WalletDI } from "@marlowe.io/wallet";
import * as Big from "@marlowe.io/adapter/bigint";
@@ -60,10 +53,7 @@ export interface ApplicableActionsAPI {
* as an upper bound.
* @returns An object with an array of {@link ApplicableAction | applicable actions} and the {@link ContractDetails | contract details}
*/
- getApplicableActions(
- contractDetails: ContractDetails,
- environment?: Environment
- ): Promise;
+ getApplicableActions(contractDetails: ContractDetails, environment?: Environment): Promise;
/**
* Converts an {@link ApplicableAction} into an {@link ApplicableInput}.
@@ -83,7 +73,8 @@ export interface ApplicableActionsAPI {
getInput(contractDetails: ActiveContract, action: CanChoose, chosenNum: ChosenNum): Promise;
/**
- * Applies an input to a contract. This is a wrapper around the {@link ContractsAPI.applyInputs | Contracts's API applyInputs} function.
+ * Applies an {@link ApplicableInput} to a contract.
+ * @returns The transaction id of the applied input.
*/
applyInput(contractId: ContractId, request: ApplyApplicableInputRequest): Promise;
/**
@@ -131,9 +122,7 @@ export interface ApplyApplicableInputRequest {
/**
* @hidden
*/
-export function mkApplicableActionsAPI(
- di: RestDI & WalletDI & GetContinuationDI & ChainTipDI
-): ApplicableActionsAPI {
+export function mkApplicableActionsAPI(di: RestDI & WalletDI & GetContinuationDI & ChainTipDI): ApplicableActionsAPI {
async function mkFilter(): Promise;
async function mkFilter(contractDetails: ActiveContract): Promise;
async function mkFilter(
@@ -156,16 +145,10 @@ export function mkApplicableActionsAPI(
};
}
-export type ApplyInputDI = (
- contractId: ContractId,
- request: ApplyInputsRequest
-) => Promise;
+export type ApplyInputDI = (contractId: ContractId, request: ApplyInputsRequest) => Promise;
function applyInput(doApply: ApplyInputDI) {
- return async function (
- contractId: ContractId,
- request: ApplyApplicableInputRequest
- ): Promise {
+ return async function (contractId: ContractId, request: ApplyApplicableInputRequest): Promise {
return doApply(contractId, {
inputs: request.input.inputs,
tags: request.tags,
@@ -210,7 +193,7 @@ export interface CanNotify {
/**
* Discriminator field, used to differentiate the action type.
*/
- actionType: "Notify";
+ type: "Notify";
/**
* If the When's case is merkleized, this is the hash of the merkleized continuation.
*/
@@ -229,7 +212,7 @@ export interface CanDeposit {
/**
* Discriminator field, used to differentiate the action type.
*/
- actionType: "Deposit";
+ type: "Deposit";
/**
* If the When's case is merkleized, this is the hash of the merkleized continuation.
*/
@@ -252,7 +235,7 @@ export interface CanChoose {
/**
* Discriminator field, used to differentiate the action type.
*/
- actionType: "Choice";
+ type: "Choice";
/**
* If the When's case is merkleized, this is the hash of the merkleized continuation.
*/
@@ -275,7 +258,7 @@ export interface CanChoose {
* @category ApplicableActionsAPI
*/
export interface CanAdvance {
- actionType: "Advance";
+ type: "Advance";
environment: Environment;
}
@@ -334,7 +317,7 @@ export function getApplicableInput(di: GetContinuationDI) {
}
const environment = action.environment;
- switch (action.actionType) {
+ switch (action.type) {
case "Advance":
return {
inputs: [],
@@ -412,7 +395,7 @@ export function simulateApplicableInput(
}
function getApplicant(action: ApplicableAction): ActionApplicant {
- switch (action.actionType) {
+ switch (action.type) {
case "Notify":
case "Advance":
return "anybody";
@@ -461,10 +444,7 @@ type GetApplicableActionsDI = GetContinuationDI & ChainTipDI;
* @hidden
*/
export function getApplicableActions(di: GetApplicableActionsDI) {
- return async function (
- contractDetails: ContractDetails,
- environment?: Environment
- ): Promise {
+ return async function (contractDetails: ContractDetails, environment?: Environment): Promise {
// If the contract is closed there are no applicable actions
if (contractDetails.type === "closed") return [];
@@ -480,7 +460,7 @@ export function getApplicableActions(di: GetApplicableActionsDI) {
let applicableActions: ApplicableAction[] = initialReduce.reduced
? [
{
- actionType: "Advance",
+ type: "Advance",
environment: env,
},
]
@@ -673,7 +653,7 @@ const flattenChoices = {
concat: (fst: CanChoose, snd: CanChoose): CanChoose => {
const mergedBounds = mergeBounds(fst.choice.choose_between.concat(snd.choice.choose_between));
return {
- actionType: "Choice",
+ type: "Choice",
environment: fst.environment,
choice: {
for_choice: fst.choice.for_choice,
@@ -709,7 +689,7 @@ function getApplicableActionFromCase(env: Environment, state: MarloweState, aCas
if (isDepositAction(aCase.case)) {
const deposit = aCase.case;
return accumulatorFromDeposit(env, state, {
- actionType: "Deposit",
+ type: "Deposit",
deposit,
environment: env,
});
@@ -717,7 +697,7 @@ function getApplicableActionFromCase(env: Environment, state: MarloweState, aCas
const choice = aCase.case;
return accumulatorFromChoice({
- actionType: "Choice",
+ type: "Choice",
choice,
environment: env,
});
@@ -728,7 +708,7 @@ function getApplicableActionFromCase(env: Environment, state: MarloweState, aCas
}
return accumulatorFromNotify({
- actionType: "Notify",
+ type: "Notify",
environment: env,
});
}
diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts
index fe437486..8de79ee5 100644
--- a/packages/runtime/lifecycle/src/generic/contracts.ts
+++ b/packages/runtime/lifecycle/src/generic/contracts.ts
@@ -1,18 +1,8 @@
import { Option } from "fp-ts/lib/Option.js";
-import {
- Contract,
- Environment,
- Input,
- Party,
- RoleName,
-} from "@marlowe.io/language-core-v1";
+import { Contract, Environment, Input, Party, RoleName } from "@marlowe.io/language-core-v1";
import { unsafeTaskEither } from "@marlowe.io/adapter/fp-ts";
-import {
- getAddressesAndCollaterals,
- WalletAPI,
- WalletDI,
-} from "@marlowe.io/wallet/api";
+import { getAddressesAndCollaterals, WalletAPI, WalletDI } from "@marlowe.io/wallet/api";
import {
PolicyId,
ContractId,
@@ -25,13 +15,7 @@ import {
Tags,
} from "@marlowe.io/runtime-core";
-import {
- FPTSRestAPI,
- RestClient,
- RestDI,
- ItemRange,
- DeprecatedRestDI,
-} from "@marlowe.io/runtime-rest-client";
+import { FPTSRestAPI, RestClient, RestDI, ItemRange, DeprecatedRestDI } from "@marlowe.io/runtime-rest-client";
import { Next, noNext } from "@marlowe.io/language-core-v1/next";
import {
@@ -57,15 +41,12 @@ export type ContractsDI = WalletDI & RestDI;
* Both options share the same {@link CreateContractRequestBase | request parameters}.
* @category ContractsAPI
*/
-export type CreateContractRequest =
- | CreateContractRequestFromContract
- | CreateContractRequestFromBundle;
+export type CreateContractRequest = CreateContractRequestFromContract | CreateContractRequestFromBundle;
/**
* @category ContractsAPI
*/
-export interface CreateContractRequestFromContract
- extends CreateContractRequestBase {
+export interface CreateContractRequestFromContract extends CreateContractRequestBase {
/**
* The Marlowe Contract to create
*/
@@ -75,8 +56,7 @@ export interface CreateContractRequestFromContract
/**
* @category ContractsAPI
*/
-export interface CreateContractRequestFromBundle
- extends CreateContractRequestBase {
+export interface CreateContractRequestFromBundle extends CreateContractRequestBase {
/**
* The Marlowe Object bundle to create
*/
@@ -257,6 +237,7 @@ export interface CreateContractRequestBase {
}
/**
+ * Request parameters used by {@link api.ContractsAPI#applyInputs | applyInputs}.
* @category ContractsAPI
*/
export type ApplyInputsRequest = {
@@ -278,9 +259,7 @@ export interface ContractsAPI {
* @returns ContractId (Marlowe id) and TxId (Cardano id) of the submitted Tx
* @throws DecodingError
*/
- createContract(
- createContractRequest: CreateContractRequest
- ): Promise<[ContractId, TxId]>;
+ createContract(createContractRequest: CreateContractRequest): Promise<[ContractId, TxId]>;
/**
* Submit to the Cardano Ledger, the Transaction(Tx) that will apply inputs to a given created contract.
@@ -288,18 +267,12 @@ export interface ContractsAPI {
* @param applyInputsRequest inputs to apply
* @throws DecodingError
*/
- applyInputs(
- contractId: ContractId,
- applyInputsRequest: ApplyInputsRequest
- ): Promise;
+ applyInputs(contractId: ContractId, applyInputsRequest: ApplyInputsRequest): Promise;
/**
* @deprecated Deprecated in favour of {@link @marlowe.io/runtime-lifecycle!api.ApplicableActionsAPI}
*/
- getApplicableInputs(
- contractId: ContractId,
- environement: Environment
- ): Promise;
+ getApplicableInputs(contractId: ContractId, environement: Environment): Promise;
/**
* @description
@@ -343,10 +316,7 @@ export const getInputHistory =
})
)
);
- const sortOptionalBlock = (
- a: Option,
- b: Option
- ) => {
+ const sortOptionalBlock = (a: Option, b: Option) => {
if (a._tag === "None" || b._tag === "None") {
// TODO: to avoid this error we should provide a higer level function that gets the transactions as the different
// status and with the appropiate values for each state.
@@ -387,9 +357,7 @@ export const getInputHistory =
export const createContract =
({ wallet, restClient }: ContractsDI) =>
- async (
- createContractRequest: CreateContractRequest
- ): Promise<[ContractId, TxId]> => {
+ async (createContractRequest: CreateContractRequest): Promise<[ContractId, TxId]> => {
const addressesAndCollaterals = await getAddressesAndCollaterals(wallet);
const baseRequest: BuildCreateContractTxRequestOptions = {
@@ -405,8 +373,7 @@ export const createContract =
tags: createContractRequest.tags,
metadata: createContractRequest.metadata,
- minimumLovelaceUTxODeposit:
- createContractRequest.minimumLovelaceUTxODeposit,
+ minimumLovelaceUTxODeposit: createContractRequest.minimumLovelaceUTxODeposit,
};
let restClientRequest: BuildCreateContractTxRequest;
@@ -425,13 +392,10 @@ export const createContract =
sourceId: contractSources.contractSourceId,
};
}
- const buildCreateContractTxResponse =
- await restClient.buildCreateContractTx(restClientRequest);
+ const buildCreateContractTxResponse = await restClient.buildCreateContractTx(restClientRequest);
const contractId = buildCreateContractTxResponse.contractId;
- const hexTransactionWitnessSet = await wallet.signTx(
- buildCreateContractTxResponse.tx.cborHex
- );
+ const hexTransactionWitnessSet = await wallet.signTx(buildCreateContractTxResponse.tx.cborHex);
await restClient.submitContract({
contractId,
@@ -443,64 +407,40 @@ export const createContract =
const getApplicableInputs =
({ wallet, deprecatedRestAPI }: ContractsDI & DeprecatedRestDI) =>
async (contractId: ContractId, environement: Environment): Promise => {
- const contractDetails = await unsafeTaskEither(
- deprecatedRestAPI.contracts.contract.get(contractId)
- );
+ const contractDetails = await unsafeTaskEither(deprecatedRestAPI.contracts.contract.get(contractId));
if (!contractDetails.state) {
return noNext;
} else {
- const parties = await getParties(wallet)(
- contractDetails.roleTokenMintingPolicyId
- );
- return await unsafeTaskEither(
- deprecatedRestAPI.contracts.contract.next(contractId)(environement)(
- parties
- )
- );
+ const parties = await getParties(wallet)(contractDetails.roleTokenMintingPolicyId);
+ return await unsafeTaskEither(deprecatedRestAPI.contracts.contract.next(contractId)(environement)(parties));
}
};
const getContractIds =
({ deprecatedRestAPI, wallet }: ContractsDI & DeprecatedRestDI) =>
async (): Promise => {
- const partyAddresses = [
- await wallet.getChangeAddress(),
- ...(await wallet.getUsedAddresses()),
- ];
+ const partyAddresses = [await wallet.getChangeAddress(), ...(await wallet.getUsedAddresses())];
const kwargs = { tags: [], partyAddresses, partyRoles: [] };
- const loop = async (
- acc: ContractId[],
- range?: ItemRange
- ): Promise => {
- const result =
- await deprecatedRestAPI.contracts.getHeadersByRange(range)(kwargs)();
+ const loop = async (acc: ContractId[], range?: ItemRange): Promise => {
+ const result = await deprecatedRestAPI.contracts.getHeadersByRange(range)(kwargs)();
if (result._tag === "Left") throw result.left;
const response = result.right;
- const contractIds = [
- ...acc,
- ...response.contracts.map(({ contractId }) => contractId),
- ];
- return response.page.next
- ? loop(contractIds, response.page.next)
- : contractIds;
+ const contractIds = [...acc, ...response.contracts.map(({ contractId }) => contractId)];
+ return response.page.next ? loop(contractIds, response.page.next) : contractIds;
};
return loop([]);
};
-const getParties: (
- walletApi: WalletAPI
-) => (roleTokenMintingPolicyId: PolicyId) => Promise =
+const getParties: (walletApi: WalletAPI) => (roleTokenMintingPolicyId: PolicyId) => Promise =
(walletAPI) => async (roleMintingPolicyId) => {
const changeAddress: Party = await walletAPI
.getChangeAddress()
.then((addressBech32) => ({ address: addressBech32 }));
- const usedAddresses: Party[] = await walletAPI
- .getUsedAddresses()
- .then((addressesBech32) =>
- addressesBech32.map((addressBech32) => ({
- address: addressBech32,
- }))
- );
+ const usedAddresses: Party[] = await walletAPI.getUsedAddresses().then((addressesBech32) =>
+ addressesBech32.map((addressBech32) => ({
+ address: addressBech32,
+ }))
+ );
const roles: Party[] = (await walletAPI.getTokens())
.filter((token) => token.assetId.policyId == roleMintingPolicyId)
.map((token) => ({ role_token: token.assetId.policyId }));
@@ -509,10 +449,7 @@ const getParties: (
export const applyInputs =
({ wallet, restClient }: ContractsDI) =>
- async (
- contractId: ContractId,
- applyInputsRequest: ApplyInputsRequest
- ): Promise => {
+ async (contractId: ContractId, applyInputsRequest: ApplyInputsRequest): Promise => {
const addressesAndCollaterals = await getAddressesAndCollaterals(wallet);
const envelope = await restClient.applyInputsToContract({
contractId,
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index a87eaf69..784a2aa8 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -1,9 +1,4 @@
-import {
- ContractId,
- PolicyId,
- TxId,
- contractIdToTxId,
-} from "@marlowe.io/runtime-core";
+import { ContractId, PolicyId, TxId, contractIdToTxId } from "@marlowe.io/runtime-core";
import {
ApplyInputsRequest,
CreateContractRequest,
@@ -11,24 +6,14 @@ import {
applyInputs,
getInputHistory,
} from "./contracts.js";
-import {
- SingleInputTx,
- TransactionSuccess,
-} from "@marlowe.io/language-core-v1/semantics";
-import {
- ChosenNum,
- Contract,
- Environment,
- MarloweState,
-} from "@marlowe.io/language-core-v1";
-import { RestClient, RestDI } from "@marlowe.io/runtime-rest-client";
-import { WalletAPI, WalletDI } from "@marlowe.io/wallet";
+import { SingleInputTx, TransactionSuccess } from "@marlowe.io/language-core-v1/semantics";
+import { ChosenNum, Contract, Environment, MarloweState } from "@marlowe.io/language-core-v1";
+import { RestDI } from "@marlowe.io/runtime-rest-client";
+import { WalletDI } from "@marlowe.io/wallet";
import {
ApplicableAction,
- ApplicableActionsFilter,
ApplicableInput,
ApplyApplicableInputRequest,
- ApplyInputDI,
CanAdvance,
CanChoose,
CanDeposit,
@@ -37,7 +22,7 @@ import {
GetContinuationDI,
} from "./applicable-actions.js";
import * as Applicable from "./applicable-actions.js";
-import { ContractSourceId } from "@marlowe.io/marlowe-object";
+import { empty } from "fp-ts/lib/ReadonlyRecord.js";
/**
*
@@ -47,28 +32,43 @@ import { ContractSourceId } from "@marlowe.io/marlowe-object";
export type ContractsDI = WalletDI & RestDI & GetContinuationDI & ChainTipDI;
/**
- * TODO comment
+ * This Interface provides capabilities for runnning a Contract over Cardano.
* @category New ContractsAPI
*/
export interface ContractsAPI {
- createContract(
- createContractRequest: CreateContractRequest
- ): Promise;
- loadContract(contractId: ContractId): Promise;
+ /**
+ * Submit to the Cardano Ledger, the Transaction(Tx) that will create the Marlowe Contract passed in the request.
+ * It doesn't wait for the transaction to be confirmed on the Cardano blockchain.
+ * @param createContractRequest Request parameters for creating a Marlowe Contract on Cardano
+ * @returns A contract instance API that can be used to interact with the contract newly created
+ */
+ create(request: CreateContractRequest): Promise;
+
+ /**
+ * Load a contract instance API for a given contract id.
+ * @param id The contract id of the contract instance
+ * @returns A contract instance API that can be used to interact with the contract
+ */
+ load(id: ContractId): Promise;
}
+/**
+ * This function creates a ContractsAPI instance.
+ * @param di Dependency Injection for the Contract API
+ * @returns ContractsAPI instance
+ */
export function mkContractsAPI(di: ContractsDI): ContractsAPI {
// The ContractInstance API is stateful as it has some cache, so whenever
// possible we want to reuse the same instance of the API for the same contractId
const apis = new Map();
return {
- createContract: async (request) => {
+ create: async (request) => {
const [contractId, _] = await createContract(di)(request);
apis.set(contractId, mkContractInstanceAPI(di, contractId));
return apis.get(contractId)!;
},
- loadContract: async (contractId) => {
+ load: async (contractId) => {
if (apis.has(contractId)) {
return apis.get(contractId)!;
} else {
@@ -78,14 +78,46 @@ export function mkContractsAPI(di: ContractsDI): ContractsAPI {
};
}
+/**
+ * This Interface provides capabilities for evaluating the applicable actions for a contract instance.
+ * An applicable action is an action that can be applied to a contract instance in a given environment.
+ * @category New ContractsAPI
+ **/
export interface ApplicableActionsAPI {
+ /**
+ * A list of all the applicable actions for the contract instance regarless of the role of the wallet
+ * @returns A list of applicable actions
+ */
actions: ApplicableAction[];
+ /**
+ * A list of all the applicable actions for the contract instance that the wallet can perform
+ * @returns A list of applicable actions
+ */
myActions: ApplicableAction[];
- toInput(
- action: CanNotify | CanDeposit | CanAdvance
- ): Promise;
+ /**
+ * Convert an applicable action to an applicable input
+ * @param action An applicable action
+ * @returns An applicable input
+ */
+ toInput(action: CanNotify | CanDeposit | CanAdvance): Promise;
+ /**
+ * Convert an applicable action to an applicable input
+ * @param action An applicable action
+ * @param chosenNum The number chosen in the choice action
+ * @returns An applicable input
+ */
toInput(action: CanChoose, chosenNum: ChosenNum): Promise;
+ /**
+ * Simulates the result of applying an input to the contract instance
+ * @param input An applicable input
+ * @returns The result of applying the input
+ */
simulate(input: ApplicableInput): TransactionSuccess;
+ /**
+ * Apply an input to the contract instance
+ * @param req Request parameters for applying an input to the contract instance
+ * @returns The transaction id of the transaction that applied the input
+ */
apply(req: ApplyApplicableInputRequest): Promise;
}
@@ -105,19 +137,11 @@ function mkApplicableActionsAPI(
const standaloneAPI = Applicable.mkApplicableActionsAPI(di);
- async function toInput(
- action: CanNotify | CanDeposit | CanAdvance
- ): Promise;
- async function toInput(
- action: CanChoose,
- chosenNum: ChosenNum
- ): Promise;
- async function toInput(
- action: ApplicableAction,
- chosenNum?: ChosenNum
- ): Promise {
+ async function toInput(action: CanNotify | CanDeposit | CanAdvance): Promise;
+ async function toInput(action: CanChoose, chosenNum: ChosenNum): Promise;
+ async function toInput(action: ApplicableAction, chosenNum?: ChosenNum): Promise {
const activeContractDetails = getActiveContractDetails();
- if (action.actionType === "Choice") {
+ if (action.type === "Choice") {
return standaloneAPI.getInput(activeContractDetails, action, chosenNum!);
} else {
return standaloneAPI.getInput(activeContractDetails, action);
@@ -137,75 +161,105 @@ function mkApplicableActionsAPI(
}
/**
- * TODO comment
+ * Request parameters for evaluating the applicable actions for a contract instance.
* @category New ContractsAPI
*/
-export type ComputeApplicableActionsRequest = {
+export type EvaluateApplicableActionsRequest = {
+ /*
+ * Applicable actions are evaluated in the context of an environment (time interval of execution on the Cardano Ledger).
+ */
environment?: Environment;
- contractDetails?: ContractDetails;
};
/**
- * TODO comment
+ * This Interface provides capabilities for interacting with a Contract Instance.
+ * A Contract Instance is a contract that has been created on the Cardano blockchain.
* @category New ContractsAPI
*/
export interface ContractInstanceAPI {
- contractId: ContractId;
+ /**
+ * The contract Id of the contract instance
+ */
+ id: ContractId;
+ /**
+ * Wait for the transaction that created the contract to be confirmed on the Cardano blockchain.
+ * @returns A boolean value indicating whether the transaction has been confirmed or not.
+ */
waitForConfirmation: () => Promise;
- getContractDetails: () => Promise;
- applyInputs(applyInputsRequest: ApplyInputsRequest): Promise;
- computeApplicableActions(
- request?: ComputeApplicableActionsRequest
- ): Promise;
+ /**
+ * Get the details of the contract instance
+ * @returns The details of the contract instance
+ */
+ getDetails: () => Promise;
+ /**
+ * Check if the contract instance is active
+ * @returns A boolean value indicating whether the contract instance is active or not
+ */
+ isActive: () => Promise;
+ /**
+ * Check if the contract instance is closed
+ * @returns A boolean value indicating whether the contract instance is closed or not
+ */
+ isClosed: () => Promise;
+ /**
+ * Apply inputs to the contract instance
+ * @param applyInputsRequest Request parameters for applying inputs to the contract instance
+ * @returns The transaction id of the transaction that applied the inputs
+ */
+ applyInput(request: ApplyApplicableInputRequest): Promise;
+ /**
+ * Compute the applicable actions for the contract instance
+ * @param request Request parameters for computing the applicable actions
+ * @returns ApplicableActionsAPI instance
+ */
+ evaluateApplicableActions(request?: EvaluateApplicableActionsRequest): Promise;
/**
* Get a list of the applied inputs for the contract
*/
getInputHistory(): Promise;
}
-function mkContractInstanceAPI(
- di: ContractsDI & GetContinuationDI & ChainTipDI,
- contractId: ContractId
-): ContractInstanceAPI {
- const contractCreationTxId = contractIdToTxId(contractId);
+function mkContractInstanceAPI(di: ContractsDI & GetContinuationDI & ChainTipDI, id: ContractId): ContractInstanceAPI {
+ const contractCreationTxId = contractIdToTxId(id);
const applicableActionsAPI = Applicable.mkApplicableActionsAPI(di);
return {
- contractId,
+ id,
waitForConfirmation: async () => {
- return di.wallet.waitConfirmation(contractCreationTxId);
+ // Todo : This is a temporary solution. We need to implement a better way to get the last transaction.
+ const txs = await di.restClient.getTransactionsForContract({ contractId: id }).then((res) => res.transactions);
+ if (txs.length === 0) {
+ return di.wallet.waitConfirmation(contractCreationTxId);
+ } else {
+ return di.wallet.waitConfirmation(txs[txs.length - 1].transactionId);
+ }
},
- getContractDetails: async () => {
- return getContractDetails(di)(contractId);
+ getDetails: async () => {
+ return getContractDetails(di)(id);
},
- computeApplicableActions: async (req = {}) => {
- const contractDetails =
- req.contractDetails ?? (await getContractDetails(di)(contractId));
- const actions = await applicableActionsAPI.getApplicableActions(
- contractDetails,
- req.environment
- );
+ evaluateApplicableActions: async (req = {}) => {
+ const contractDetails = await getContractDetails(di)(id);
+ const actions = await applicableActionsAPI.getApplicableActions(contractDetails, req.environment);
let myActions = [] as ApplicableAction[];
if (contractDetails.type === "active") {
- const myActionsFilter =
- await applicableActionsAPI.mkFilter(contractDetails);
+ const myActionsFilter = await applicableActionsAPI.mkFilter(contractDetails);
myActions = actions.filter(myActionsFilter);
}
- return mkApplicableActionsAPI(
- di,
- actions,
- myActions,
- contractDetails,
- contractId
- );
+ return mkApplicableActionsAPI(di, actions, myActions, contractDetails, id);
+ },
+ applyInput: async (request) => {
+ return applicableActionsAPI.applyInput(id, request);
+ },
+ isActive: async () => {
+ return (await getContractDetails(di)(id)).type === "active";
},
- applyInputs: async (request) => {
- return applyInputs(di)(contractId, request);
+ isClosed: async () => {
+ return (await getContractDetails(di)(id)).type === "closed";
},
getInputHistory: async () => {
// TODO: We can optimize this by only asking for the new transaction headers
// and only asking for contract details of the new transactions.
- return getInputHistory(di)(contractId);
+ return getInputHistory(di)(id);
},
};
}
@@ -213,10 +267,7 @@ function mkContractInstanceAPI(
function getContractDetails(di: ContractsDI) {
return async function (contractId: ContractId): Promise {
const contractDetails = await di.restClient.getContractById({ contractId });
- if (
- typeof contractDetails.state === "undefined" ||
- typeof contractDetails.currentContract === "undefined"
- ) {
+ if (typeof contractDetails.state === "undefined" || typeof contractDetails.currentContract === "undefined") {
return { type: "closed" };
} else {
return {
@@ -231,7 +282,8 @@ function getContractDetails(di: ContractsDI) {
}
/**
- * TODO comment
+ * A closed contract is a contract that has been closed and is no longer active.
+ * It is still stored in the Cardano ledger, but it cannot receive inputs or advance its state anymore.
* @category New ContractsAPI
*/
export type ClosedContract = {
@@ -239,7 +291,8 @@ export type ClosedContract = {
};
/**
- * TODO comment
+ * An active contract is a contract appended to a Cardano ledger that is not closed.
+ * It can receive inputs and advance its state.
* @category New ContractsAPI
*/
export type ActiveContract = {
@@ -251,8 +304,7 @@ export type ActiveContract = {
};
/**
- * TODO comment
- * TODO: Fill with all the information we want to expose
+ * Represents the details of a contract, either active or closed.
* @category New ContractsAPI
*/
export type ContractDetails = ClosedContract | ActiveContract;
diff --git a/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts b/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts
index 6bc97c55..137b3b95 100644
--- a/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts
+++ b/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts
@@ -1,13 +1,12 @@
import { pipe } from "fp-ts/lib/function.js";
import { addDays } from "date-fns";
-import { datetoTimeout, Input, MarloweState } from "@marlowe.io/language-core-v1";
+import { datetoTimeout } from "@marlowe.io/language-core-v1";
import console from "console";
-import { ContractId, payoutId, runtimeTokenToMarloweTokenValue } from "@marlowe.io/runtime-core";
+import { runtimeTokenToMarloweTokenValue } from "@marlowe.io/runtime-core";
import { MINUTES } from "@marlowe.io/adapter/time";
import { AtomicSwap } from "@marlowe.io/language-examples";
-import { RestClient } from "@marlowe.io/runtime-rest-client";
import {
generateSeedPhrase,
logDebug,
@@ -19,7 +18,7 @@ import {
} from "@marlowe.io/testing-kit";
import { AxiosError } from "axios";
import { MarloweJSON } from "@marlowe.io/adapter/codec";
-import { onlyByContractIds, RuntimeLifecycle } from "@marlowe.io/runtime-lifecycle/api";
+import { CanDeposit, onlyByContractIds } from "@marlowe.io/runtime-lifecycle/api";
import { mintRole } from "@marlowe.io/runtime-rest-client/contract";
@@ -55,7 +54,7 @@ describe("swap", () => {
await logWalletInfo("buyer", buyer.wallet);
const sellerLifecycle = mkLifecycle(seller.wallet);
const buyerLifecycle = mkLifecycle(buyer.wallet);
- const bankLifecycle = mkLifecycle(bank);
+ const anyoneLifecycle = mkLifecycle(bank);
const scheme: AtomicSwap.Scheme = {
offer: {
@@ -77,69 +76,62 @@ describe("swap", () => {
logDebug(`contract: ${MarloweJSON.stringify(swapContract, null, 4)}`);
logInfo("Contract Creation");
- const openroleCOnfig = mintRole("OpenRole");
- logInfo(`Config ${MarloweJSON.stringify(openroleCOnfig, null, 4)}`);
- const [contractId, txCreatedContract] = await sellerLifecycle.contracts.createContract({
+ const sellerContractInstance = await sellerLifecycle.newContractAPI.create({
contract: swapContract,
- minimumLovelaceUTxODeposit: 3_000_000,
- roles: {
- [scheme.ask.buyer.role_token]: openroleCOnfig,
- },
+ roles: { [scheme.ask.buyer.role_token]: mintRole("OpenRole") },
});
- logInfo("Contract Created");
- await seller.wallet.waitConfirmation(txCreatedContract);
+ sellerContractInstance.waitForConfirmation();
await seller.wallet.waitRuntimeSyncingTillCurrentWalletTip(sellerLifecycle.restClient);
+ expect(await sellerContractInstance.isActive()).toBeTruthy();
+
+ logInfo(`contract created : ${sellerContractInstance.id}`);
logInfo(`Seller > Provision Offer`);
- let actions = await getAvailableActions(sellerLifecycle, scheme, contractId);
+ let applicableActions = await sellerContractInstance.evaluateApplicableActions();
- expect(actions.length).toBe(1);
- expect(actions[0].typeName).toBe("ProvisionOffer");
- const provisionOffer = actions[0] as AtomicSwap.ProvisionOffer;
- const offerProvisionedTx = await sellerLifecycle.contracts.applyInputs(contractId, {
- inputs: [provisionOffer.input],
- });
+ expect(applicableActions.myActions.map((a) => a.type)).toBe(["Deposit"]);
+ const provisionOffer = await applicableActions.toInput(applicableActions.myActions[0] as CanDeposit);
+
+ await sellerContractInstance.applyInput({ input: provisionOffer });
- await seller.wallet.waitConfirmation(offerProvisionedTx);
+ await sellerContractInstance.waitForConfirmation();
await seller.wallet.waitRuntimeSyncingTillCurrentWalletTip(sellerLifecycle.restClient);
logInfo(`Buyer > Swap`);
- actions = await getAvailableActions(sellerLifecycle, scheme, contractId);
+ const buyerContractInstance = await buyerLifecycle.newContractAPI.load(sellerContractInstance.id);
+ applicableActions = await buyerContractInstance.evaluateApplicableActions();
+ expect(applicableActions.myActions.map((a) => a.type)).toBe(["Deposit"]);
- expect(actions.length).toBe(2);
- expect(actions[0].typeName).toBe("Swap");
- expect(actions[1].typeName).toBe("Retract");
- const swap = actions[0] as AtomicSwap.Swap;
- const swappedTx = await buyerLifecycle.contracts.applyInputs(contractId, { inputs: [swap.input] });
+ const swap = await applicableActions.toInput(applicableActions.myActions[0] as CanDeposit);
- await buyer.wallet.waitConfirmation(swappedTx);
- await buyer.wallet.waitRuntimeSyncingTillCurrentWalletTip(buyerLifecycle.restClient);
+ await buyerContractInstance.applyInput({ input: swap });
- logInfo(`Anyone > Confirm Swap`);
+ await buyerContractInstance.waitForConfirmation();
+ await buyer.wallet.waitRuntimeSyncingTillCurrentWalletTip(sellerLifecycle.restClient);
- actions = await getAvailableActions(bankLifecycle, scheme, contractId);
+ logInfo(`Anyone > Confirm Swap`);
- expect(actions.length).toBe(1);
- expect(actions[0].typeName).toBe("ConfirmSwap");
- const confirmSwap = actions[0] as AtomicSwap.ConfirmSwap;
- const swapConfirmedTx = await bankLifecycle.contracts.applyInputs(contractId, { inputs: [confirmSwap.input] });
+ const anyoneContractInstance = await anyoneLifecycle.newContractAPI.load(sellerContractInstance.id);
+ applicableActions = await buyerContractInstance.evaluateApplicableActions();
+ expect(applicableActions.myActions.map((a) => a.type)).toBe(["Notify"]);
- await bank.waitConfirmation(swapConfirmedTx);
- await bank.waitRuntimeSyncingTillCurrentWalletTip(bankLifecycle.restClient);
+ await anyoneContractInstance.applyInput({ input: swap });
- const closedState = await getClosedState(bankLifecycle, scheme, contractId);
+ await anyoneContractInstance.waitForConfirmation();
+ await buyer.wallet.waitRuntimeSyncingTillCurrentWalletTip(sellerLifecycle.restClient);
- expect(closedState.reason.typeName).toBe("Swapped");
+ expect(anyoneContractInstance.isClosed).toBeTruthy();
logInfo(`Buyer > Retrieve Payout`);
- const buyerPayouts = await buyerLifecycle.payouts.available(onlyByContractIds([contractId]));
+ const buyerPayouts = await buyerLifecycle.payouts.available(onlyByContractIds([buyerContractInstance.id]));
expect(buyerPayouts.length).toBe(1);
await buyerLifecycle.payouts.withdraw([buyerPayouts[0].payoutId]);
+ logInfo(`Swapped Completed`);
await logWalletInfo("seller", seller.wallet);
await logWalletInfo("buyer", buyer.wallet);
} catch (e) {
@@ -152,45 +144,3 @@ describe("swap", () => {
10 * MINUTES
);
});
-
-const getClosedState = async (
- runtimeLifecycle: RuntimeLifecycle,
- scheme: AtomicSwap.Scheme,
- contractId: ContractId
-): Promise => {
- const inputHistory = await runtimeLifecycle.contracts.getInputHistory(contractId);
-
- await shouldBeAClosedContract(runtimeLifecycle.restClient, contractId);
-
- return AtomicSwap.getClosedState(scheme, inputHistory);
-};
-
-const getAvailableActions = async (
- runtimeLifecycle: RuntimeLifecycle,
- scheme: AtomicSwap.Scheme,
- contractId: ContractId
-): Promise => {
- const inputHistory = await runtimeLifecycle.contracts.getInputHistory(contractId);
-
- const marloweState = await getMarloweStatefromAnActiveContract(runtimeLifecycle.restClient, contractId);
- const now = datetoTimeout(new Date());
- const contractState = AtomicSwap.getActiveState(scheme, now, inputHistory, marloweState);
- return AtomicSwap.getAvailableActions(scheme, contractState);
-};
-
-const shouldBeAClosedContract = async (restClient: RestClient, contractId: ContractId): Promise => {
- const state = await restClient.getContractById({ contractId }).then((contractDetails) => contractDetails.state);
- if (state) {
- throw new Error("Contract retrieved is not Closed");
- } else {
- return;
- }
-};
-
-const shouldBeAnActiveContract = (state?: MarloweState): MarloweState => {
- if (state) return state;
- else throw new Error("Contract retrieved is not Active");
-};
-
-const getMarloweStatefromAnActiveContract = (restClient: RestClient, contractId: ContractId): Promise =>
- restClient.getContractById({ contractId }).then((contractDetails) => shouldBeAnActiveContract(contractDetails.state));
diff --git a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts
index 7a73102b..497d6805 100644
--- a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts
+++ b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts
@@ -17,12 +17,10 @@ describe("Runtime Contract Lifecycle ", () => {
try {
const { bank, mkLifecycle } = await readTestConfiguration().then(mkTestEnvironment({}));
const runtimeLifecycle = mkLifecycle(bank);
- const [contractId, txIdContractCreated] = await runtimeLifecycle.contracts.createContract({
- contract: close,
- minimumLovelaceUTxODeposit: 3_000_000,
- });
- await bank.waitConfirmation(txIdContractCreated);
- logInfo(`contractID created : ${contractId}`);
+ const contract = await runtimeLifecycle.newContractAPI.create({ contract: close });
+ await contract.waitForConfirmation();
+ logInfo(`contract created : ${contract.id}`);
+ expect(await contract.isClosed()).toBeTruthy();
} catch (e) {
const error = e as AxiosError;
logError(JSON.stringify(error.response?.data));
@@ -41,20 +39,16 @@ describe("Runtime Contract Lifecycle ", () => {
const runtime = mkLifecycle(bank);
const notifyTimeout = pipe(addDays(Date.now(), 1), datetoTimeout);
- const [contractId, txIdContractCreated] = await runtime.contracts.createContract({
- contract: oneNotifyTrue(notifyTimeout),
- minimumLovelaceUTxODeposit: 3_000_000,
- });
- await bank.waitConfirmation(txIdContractCreated);
- logInfo(
- `contractID status : ${contractId} -> ${(await runtime.restClient.getContractById({ contractId })).status}`
- );
+ const contractInstance = await runtime.newContractAPI.create({ contract: oneNotifyTrue(notifyTimeout) });
+ await contractInstance.waitForConfirmation();
+ logInfo(`contract created : ${contractInstance.id}`);
+ expect(await contractInstance.isActive()).toBeTruthy();
+
await bank.waitRuntimeSyncingTillCurrentWalletTip(runtime.restClient);
- const txIdInputsApplied = await runtime.contracts.applyInputs(contractId, {
- inputs: [inputNotify],
- });
- const result = await bank.waitConfirmation(txIdInputsApplied);
- expect(result).toBe(true);
+ await contractInstance.applyInputs({ inputs: [inputNotify] });
+ await contractInstance.waitForConfirmation();
+
+ expect(await contractInstance.isClosed()).toBeTruthy();
} catch (e) {
const error = e as AxiosError;
logError(error.message);
From 0961c3d16d9d837354a6ebbb609eab5a4756d96d Mon Sep 17 00:00:00 2001
From: Nicolas Henin
Date: Mon, 6 May 2024 08:20:16 +0200
Subject: [PATCH 12/14] account Deposit Integration (runtime V1.0.0)
---
Readme.md | 5 +
.../src/experimental-features/source-map.ts | 9 +-
examples/nodejs/src/marlowe-object-flow.ts | 10 +-
jsdelivr-npm-importmap.js | 2 -
packages/adapter/src/time.ts | 3 -
packages/language/core/v1/src/index.ts | 2 +-
packages/language/core/v1/src/semantics.ts | 12 +-
packages/language/examples/src/atomicSwap.ts | 124 ++-----
.../language/examples/test/atomicSwap.spec.ts | 171 +---------
.../language/specification-client/src/main.ts | 10 +-
.../rest/src/contract/endpoints/collection.ts | 11 +-
packages/runtime/client/rest/src/index.ts | 302 ++++++------------
.../client/rest/src/runtime/version.ts | 3 +-
packages/runtime/core/src/asset/index.ts | 42 ++-
.../core/src/contract/accountDeposits.ts | 23 ++
packages/runtime/core/src/index.ts | 1 +
packages/runtime/core/src/payout/index.ts | 6 +-
.../src/generic/applicable-actions.ts | 9 +-
.../lifecycle/src/generic/contracts.ts | 17 +-
.../lifecycle/src/generic/new-contract-api.ts | 25 +-
.../test/examples/swap.ada.token.e2e.spec.ts | 43 +--
.../test/generic/contracts.e2e.spec.ts | 39 ++-
.../test/generic/payouts.e2e.spec.ts | 108 -------
packages/testing-kit/src/environment/index.ts | 12 +-
packages/testing-kit/src/logging.ts | 2 +-
.../testing-kit/src/wallet/lucid/index.ts | 14 +-
.../src/wallet/lucid/provisionning.ts | 2 +-
packages/wallet/src/lucid/index.ts | 2 +-
28 files changed, 373 insertions(+), 636 deletions(-)
create mode 100644 packages/runtime/core/src/contract/accountDeposits.ts
delete mode 100644 packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts
diff --git a/Readme.md b/Readme.md
index 7f222a80..f45f3d74 100644
--- a/Readme.md
+++ b/Readme.md
@@ -79,3 +79,8 @@ Prototypes have been also built on top of this sdk:
To report a bug or request a new feature, please look through existing [Github Issues](https://github.com/input-output-hk/marlowe-ts-sdk/issues) before opening a new one.
To help in the development of this SDK, please refer to [this document](./doc/howToDevelop.md).
+
+
+
+
+
diff --git a/examples/nodejs/src/experimental-features/source-map.ts b/examples/nodejs/src/experimental-features/source-map.ts
index 076b61d9..d6de760f 100644
--- a/examples/nodejs/src/experimental-features/source-map.ts
+++ b/examples/nodejs/src/experimental-features/source-map.ts
@@ -9,7 +9,12 @@ import * as CoreG from "@marlowe.io/language-core-v1/guards";
import * as Obj from "@marlowe.io/marlowe-object";
import * as ObjG from "@marlowe.io/marlowe-object/guards";
-import { SingleInputTx, TransactionOutput, playSingleInputTxTrace } from "@marlowe.io/language-core-v1/semantics";
+import {
+ SingleInputTx,
+ TransactionOutput,
+ emptyState,
+ playSingleInputTxTrace,
+} from "@marlowe.io/language-core-v1/semantics";
import { RestClient } from "@marlowe.io/runtime-rest-client";
import { ContractId, TxId } from "@marlowe.io/runtime-core";
import { deepEqual } from "@marlowe.io/adapter/deep-equal";
@@ -173,7 +178,7 @@ export async function mkSourceMap(
const annotatedHistory = annotateHistoryFromClosure(closure)(history);
const main = closure.contracts.get(closure.main);
if (typeof main === "undefined") throw new Error(`Cant find main.`);
- return playSingleInputTxTrace(0n, main, annotatedHistory);
+ return playSingleInputTxTrace(emptyState(0n), main, annotatedHistory);
},
createContract: (options: CreateContractRequestBase) => {
const contract = stripAnnotations(closure.contracts.get(closure.main)!);
diff --git a/examples/nodejs/src/marlowe-object-flow.ts b/examples/nodejs/src/marlowe-object-flow.ts
index 968dcca5..1541d963 100644
--- a/examples/nodejs/src/marlowe-object-flow.ts
+++ b/examples/nodejs/src/marlowe-object-flow.ts
@@ -240,14 +240,14 @@ async function contractMenu(
const choices: Array<{
name: string;
- value: CanDeposit | CanAdvance | { actionType: "check-state" } | { actionType: "return" };
+ value: CanDeposit | CanAdvance | { type: "check-state" } | { type: "return" };
}> = [
{
name: "Re-check contract state",
- value: { actionType: "check-state" },
+ value: { type: "check-state" },
},
...applicableActions.myActions.map((action) => {
- switch (action.actionType) {
+ switch (action.type) {
case "Advance":
return {
name: "Close contract",
@@ -269,7 +269,7 @@ async function contractMenu(
}),
{
name: "Return to main menu",
- value: { actionType: "return" },
+ value: { type: "return" },
},
];
@@ -277,7 +277,7 @@ async function contractMenu(
message: "Contract menu",
choices,
});
- switch (selectedAction.actionType) {
+ switch (selectedAction.type) {
case "check-state":
return contractMenu(wallet, contractInstance, scheme, sourceMap);
case "return":
diff --git a/jsdelivr-npm-importmap.js b/jsdelivr-npm-importmap.js
index e6b7b05a..137f2635 100644
--- a/jsdelivr-npm-importmap.js
+++ b/jsdelivr-npm-importmap.js
@@ -38,8 +38,6 @@ const importMap = {
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.4.0-beta-rc1/dist/bundled/esm/version.js",
"@marlowe.io/language-examples":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.4.0-beta-rc1/dist/bundled/esm/language-examples.js",
- "@marlowe.io/language-examples/atomic-swap":
- "https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.4.0-beta-rc1/dist/bundled/esm/atomic-swap.js",
"@marlowe.io/language-specification-client":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-specification-client@0.4.0-beta-rc1/dist/bundled/esm/language-specification-client.js",
"@marlowe.io/token-metadata-client":
diff --git a/packages/adapter/src/time.ts b/packages/adapter/src/time.ts
index 86a2ed5d..e9200dff 100644
--- a/packages/adapter/src/time.ts
+++ b/packages/adapter/src/time.ts
@@ -30,9 +30,6 @@ export const waitForPredicatePromise = async (
// Predicate is already true, no need to wait
return;
}
- // Use a promise to wait for the specified interval
- const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
-
// Wait for the specified interval
await sleep(seconds);
diff --git a/packages/language/core/v1/src/index.ts b/packages/language/core/v1/src/index.ts
index 3249920f..cbd6a881 100644
--- a/packages/language/core/v1/src/index.ts
+++ b/packages/language/core/v1/src/index.ts
@@ -83,7 +83,7 @@ export {
MerkleizedNotify,
} from "./inputs.js";
-export { role, Party, Address, Role, RoleName } from "./participants.js";
+export { role, Party, Address, Role, RoleName, partiesToStrings, partyToString } from "./participants.js";
export { Payee, PayeeAccount, PayeeParty, AccountId } from "./payee.js";
diff --git a/packages/language/core/v1/src/semantics.ts b/packages/language/core/v1/src/semantics.ts
index 6fd4faee..c9c512e4 100644
--- a/packages/language/core/v1/src/semantics.ts
+++ b/packages/language/core/v1/src/semantics.ts
@@ -833,7 +833,11 @@ export function emptyState(minTime: POSIXTime): MarloweState {
* @returns The resulting state and contract, along with the accumulated warnings and payments or a {@link TransactionError}.
* @category Evaluation
*/
-export function playTrace(initialTime: POSIXTime, contract: Contract, transactions: Transaction[]): TransactionOutput {
+export function playTrace(
+ initialState: MarloweState,
+ contract: Contract,
+ transactions: Transaction[]
+): TransactionOutput {
function go(prev: TransactionOutput, txs: Transaction[]): TransactionOutput {
if (txs.length === 0) return prev;
if (!G.TransactionSuccess.is(prev)) return prev;
@@ -855,13 +859,13 @@ export function playTrace(initialTime: POSIXTime, contract: Contract, transactio
warnings: [],
payments: [],
contract,
- state: emptyState(initialTime),
+ state: initialState,
},
transactions
);
}
-export function playSingleInputTxTrace(initialTime: POSIXTime, contract: Contract, transactions: SingleInputTx[]) {
+export function playSingleInputTxTrace(initialState: MarloweState, contract: Contract, transactions: SingleInputTx[]) {
const txs = transactions.map((tx) => {
const tx_inputs = typeof tx.input === "undefined" ? [] : [tx.input];
return {
@@ -869,5 +873,5 @@ export function playSingleInputTxTrace(initialTime: POSIXTime, contract: Contrac
tx_inputs,
};
});
- return playTrace(initialTime, contract, txs);
+ return playTrace(initialState, contract, txs);
}
diff --git a/packages/language/examples/src/atomicSwap.ts b/packages/language/examples/src/atomicSwap.ts
index ae76e31a..518c5e1a 100644
--- a/packages/language/examples/src/atomicSwap.ts
+++ b/packages/language/examples/src/atomicSwap.ts
@@ -10,7 +10,7 @@
* are timeboxed. Sellers are known at the contract creation (fixed Address) and Buyers are unknown
* (This showcases a feature of marlowe that is called Open Roles.).
* There are 3 main stages :
- * - The Offer : The Sellers deposit their tokens.
+ * - The Offer : The Sellers create the contract and deposit their tokens at the same time via the initial account deposits feature.
* - The Ask : The Buyers deposit their tokens
* - The Swap Confirmation : an extra Notify input is added after the swap to avoid double-satisfaction attack (see link attached).
* (Any third participant could perform this action)
@@ -54,19 +54,18 @@
* @packageDocumentation
*/
+import { POSIXTime } from "@marlowe.io/adapter/time";
import {
Address,
Contract,
IChoice,
IDeposit,
INotify,
- Input,
MarloweState,
Role,
Timeout,
TokenValue,
close,
- datetoTimeout,
} from "@marlowe.io/language-core-v1";
import * as G from "@marlowe.io/language-core-v1/guards";
import { SingleInputTx } from "@marlowe.io/language-core-v1/semantics";
@@ -96,22 +95,7 @@ export type Scheme = {
/* #region State */
export type State = ActiveState | Closed;
-export type ActiveState = WaitingSellerOffer | NoSellerOfferInTime | WaitingForAnswer | WaitingForSwapConfirmation;
-
-export type WaitingSellerOffer = {
- type: "WaitingSellerOffer";
-};
-
-export const waitingSellerOffer: WaitingSellerOffer = {
- type: "WaitingSellerOffer",
-};
-
-export type NoSellerOfferInTime = {
- type: "NoSellerOfferInTime";
-};
-export const noSellerOfferInTime: NoSellerOfferInTime = {
- type: "NoSellerOfferInTime",
-};
+export type ActiveState = WaitingForAnswer | WaitingForSwapConfirmation;
export type WaitingForAnswer = {
type: "WaitingForAnswer";
@@ -154,10 +138,6 @@ export type Closed = {
* Action List available for the contract lifecycle.
*/
export type ApplicableAction =
- /* When Contract Created (timed out > NoOfferProvisionnedOnTime) */
- | ProvisionOffer // > OfferProvisionned
- /* When NoOfferProvisionnedOnTime (timed out > no timeout (need to be reduced to be closed))*/
- | RetrieveMinimumLovelaceAdded // > closed
/* When OfferProvisionned (timed out > NotConfirmedOnTime) */
| Retract // > closed
| Swap // > Swapped
@@ -166,17 +146,6 @@ export type ApplicableAction =
export type ActionParticipant = "buyer" | "seller" | "anybody";
-export type RetrieveMinimumLovelaceAdded = {
- type: "RetrieveMinimumLovelaceAdded";
- owner: ActionParticipant;
-};
-
-export type ProvisionOffer = {
- type: "ProvisionOffer";
- owner: ActionParticipant;
- input: IDeposit;
-};
-
export type Swap = {
type: "Swap";
owner: ActionParticipant;
@@ -198,16 +167,8 @@ export type Retract = {
/* #endregion */
/* #region Close Reason */
-export type CloseReason =
- | NoOfferProvisionnedOnTime
- | SellerRetracted
- | NotAnsweredOnTime
- | Swapped
- | SwappedButNotNotifiedOnTime;
+export type CloseReason = SellerRetracted | NotAnsweredOnTime | Swapped | SwappedButNotNotifiedOnTime;
-export type NoOfferProvisionnedOnTime = {
- type: "NoOfferProvisionnedOnTime";
-};
export type SellerRetracted = { type: "SellerRetracted" };
export type NotAnsweredOnTime = { type: "NotAnsweredOnTime" };
export type SwappedButNotNotifiedOnTime = {
@@ -243,26 +204,6 @@ export class UnexpectedClosedSwapContractState extends Error {
export const getApplicableActions = (scheme: Scheme, state: ActiveState): ApplicableAction[] => {
switch (state.type) {
- case "WaitingSellerOffer":
- return [
- {
- type: "ProvisionOffer",
- owner: "seller",
- input: {
- input_from_party: scheme.offer.seller,
- that_deposits: scheme.offer.asset.amount,
- of_token: scheme.offer.asset.token,
- into_account: scheme.offer.seller,
- },
- },
- ];
- case "NoSellerOfferInTime":
- return [
- {
- type: "RetrieveMinimumLovelaceAdded",
- owner: "anybody",
- },
- ];
case "WaitingForAnswer":
return [
{
@@ -306,26 +247,21 @@ export const getClosedState = (scheme: Scheme, inputHistory: SingleInputTx[]): C
switch (inputHistory.length) {
// Offer Provision Deadline has passed and there is one reduced applied to close the contract
case 0:
- return {
- type: "Closed",
- reason: { type: "NoOfferProvisionnedOnTime" },
- };
- case 1:
return {
type: "Closed",
reason: { type: "NotAnsweredOnTime" },
};
- case 2: {
+ case 1: {
const isRetracted =
- G.IChoice.is(inputHistory[1].input) && inputHistory[1].input.for_choice_id.choice_name == "retract";
+ G.IChoice.is(inputHistory[0].input) && inputHistory[0].input.for_choice_id.choice_name == "retract";
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
- if (isRetracted && nbDeposits === 1) {
+ if (isRetracted) {
return {
type: "Closed",
reason: { type: "SellerRetracted" },
};
}
- if (nbDeposits === 2) {
+ if (nbDeposits === 1) {
return {
type: "Closed",
reason: { type: "SwappedButNotNotifiedOnTime" },
@@ -333,10 +269,10 @@ export const getClosedState = (scheme: Scheme, inputHistory: SingleInputTx[]): C
}
break;
}
- case 3: {
+ case 2: {
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
const nbNotify = inputHistory.filter((singleInputTx) => G.INotify.is(singleInputTx.input)).length;
- if (nbDeposits === 2 && nbNotify === 1) {
+ if (nbDeposits === 1 && nbNotify === 1) {
return {
type: "Closed",
reason: { type: "Swapped" },
@@ -355,15 +291,13 @@ export const getActiveState = (
): ActiveState => {
switch (inputHistory.length) {
case 0:
- return now < scheme.offer.deadline ? { type: "WaitingSellerOffer" } : { type: "NoSellerOfferInTime" };
- case 1:
if (now < scheme.ask.deadline) {
return { type: "WaitingForAnswer" };
}
break;
- case 2: {
+ case 1: {
const nbDeposits = inputHistory.filter((singleInputTx) => G.IDeposit.is(singleInputTx.input)).length;
- if (nbDeposits === 2 && now < scheme.swapConfirmation.deadline) {
+ if (nbDeposits === 1 && now < scheme.swapConfirmation.deadline) {
return { type: "WaitingForSwapConfirmation" };
}
break;
@@ -373,22 +307,26 @@ export const getActiveState = (
throw new UnexpectedActiveSwapContractState(scheme, inputHistory, state);
};
-export function mkContract(scheme: Scheme): Contract {
- const mkOffer = (ask: Contract): Contract => {
- const depositOffer = {
- party: scheme.offer.seller,
- deposits: scheme.offer.asset.amount,
- of_token: scheme.offer.asset.token,
- into_account: scheme.offer.seller,
- };
-
- return {
- when: [{ case: depositOffer, then: ask }],
- timeout: scheme.offer.deadline,
- timeout_continuation: close,
- };
+/**
+ * Generate the initial state of the contract from a given scheme and start date.
+ * @param startDate : the start date of the contract creation
+ * @param scheme : the scheme of the contract
+ * @returns
+ */
+export const mkInitialMarloweState = (startDate: POSIXTime, scheme: Scheme): MarloweState => {
+ return {
+ accounts: [[[scheme.offer.seller, scheme.offer.asset.token], scheme.offer.asset.amount]],
+ boundValues: [],
+ choices: [],
+ minTime: startDate,
};
+};
+/**
+ * Generate the contract from the scheme.
+ * N.B : The offer asset is provisioned at the contract creation via initial account deposits.
+ */
+export function mkContract(scheme: Scheme): Contract {
const mkAsk = (confirmSwap: Contract): Contract => {
const depositAsk = {
party: scheme.ask.buyer,
@@ -450,5 +388,5 @@ export function mkContract(scheme: Scheme): Contract {
};
};
- return mkOffer(mkAsk(mkSwapConfirmation()));
+ return mkAsk(mkSwapConfirmation());
}
diff --git a/packages/language/examples/test/atomicSwap.spec.ts b/packages/language/examples/test/atomicSwap.spec.ts
index c8692d3e..1d3598d1 100644
--- a/packages/language/examples/test/atomicSwap.spec.ts
+++ b/packages/language/examples/test/atomicSwap.spec.ts
@@ -3,20 +3,18 @@ import * as G from "@marlowe.io/language-core-v1/guards";
import { expectType } from "@marlowe.io/adapter/io-ts";
import { datetoTimeout, close, Party, Payee, Input } from "@marlowe.io/language-core-v1";
-import { TransactionSuccess, emptyState, playTrace } from "@marlowe.io/language-core-v1/semantics";
+import { playTrace } from "@marlowe.io/language-core-v1/semantics";
import {
ConfirmSwap,
- ProvisionOffer,
Retract,
Swap,
getActiveState,
getApplicableActions,
getClosedState,
+ mkInitialMarloweState,
waitingForAnswer,
waitingForSwapConfirmation,
- waitingSellerOffer,
} from "../src/atomicSwap.js";
-import * as t from "io-ts/lib/index.js";
const aDeadlineInThePast = datetoTimeout(new Date("2000-05-01"));
const contractStart = datetoTimeout(new Date("2000-05-02"));
@@ -38,8 +36,8 @@ const anotherAsset = {
};
describe("Atomic Swap", () => {
- describe("is active (on 4 different states)", () => {
- it("when waiting a seller offer - WaitingSellerOffer", () => {
+ describe("is active (on 2 different states)", () => {
+ it("when waiting a for an answer - WaitingForAnswer", () => {
// Set up
const scheme: AtomicSwap.Scheme = {
offer: {
@@ -59,91 +57,9 @@ describe("Atomic Swap", () => {
const inputFlow: Input[] = [];
// Execute
-
- const contract = AtomicSwap.mkContract(scheme);
- const state = emptyState(aDeadlineInThePast);
+ const state = mkInitialMarloweState(contractStart, scheme);
// Verify
-
- const activeState = getActiveState(
- scheme,
- aGivenNow,
- inputFlow.map((input) => ({ interval: aTxInterval, input: input })),
- state
- );
- expect(activeState.type).toBe("WaitingSellerOffer");
- });
- it("when no seller offer have been provided in time - NoSellerOfferInTime", () => {
- // Set up
- const scheme: AtomicSwap.Scheme = {
- offer: {
- seller: { address: "sellerAddress" },
- deadline: aDeadlineInTheFuture,
- asset: anAsset,
- },
- ask: {
- buyer: { role_token: "buyer" },
- deadline: aDeadlineInTheFuture,
- asset: anotherAsset,
- },
- swapConfirmation: {
- deadline: aDeadlineInTheFuture,
- },
- };
- const inputFlow: Input[] = [];
-
- // Execute
-
- const contract = AtomicSwap.mkContract(scheme);
- const state = emptyState(aDeadlineInThePast);
-
- // Verify
-
- const activeState = getActiveState(
- scheme,
- aGivenNowAfterDeadline,
- inputFlow.map((input) => ({ interval: aTxInterval, input: input })),
- state
- );
- expect(activeState.type).toBe("NoSellerOfferInTime");
- });
- it("when waiting a for an answer - WaitingForAnswer", () => {
- // Set up
- const scheme: AtomicSwap.Scheme = {
- offer: {
- seller: { address: "sellerAddress" },
- deadline: aDeadlineInTheFuture,
- asset: anAsset,
- },
- ask: {
- buyer: { role_token: "buyer" },
- deadline: aDeadlineInTheFuture,
- asset: anotherAsset,
- },
- swapConfirmation: {
- deadline: aDeadlineInTheFuture,
- },
- };
- const inputFlow: Input[] = [(getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input];
-
- // Execute
-
- const { contract, state, payments, warnings } = expectType(
- G.TransactionSuccess,
- playTrace(contractStart, AtomicSwap.mkContract(scheme), [
- {
- tx_interval: aTxInterval,
- tx_inputs: inputFlow,
- },
- ])
- );
-
- // Verify
-
- expect(warnings).toStrictEqual([]);
- expect(contract).not.toBe(close);
- expect(payments).toStrictEqual([]);
-
const activeState = getActiveState(
scheme,
aGivenNow,
@@ -169,16 +85,13 @@ describe("Atomic Swap", () => {
deadline: aDeadlineInTheFuture,
},
};
- const inputFlow: Input[] = [
- (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
- (getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input,
- ];
+ const inputFlow: Input[] = [(getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input];
// Execute
const { contract, state, payments, warnings } = expectType(
G.TransactionSuccess,
- playTrace(contractStart, AtomicSwap.mkContract(scheme), [
+ playTrace(mkInitialMarloweState(contractStart, scheme), AtomicSwap.mkContract(scheme), [
{
tx_interval: aTxInterval,
tx_inputs: inputFlow,
@@ -233,7 +146,6 @@ describe("Atomic Swap", () => {
},
};
const inputFlow = [
- (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
(getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input,
(getApplicableActions(scheme, waitingForSwapConfirmation)[0] as ConfirmSwap).input,
];
@@ -242,7 +154,7 @@ describe("Atomic Swap", () => {
const { contract, payments, warnings } = expectType(
G.TransactionSuccess,
- playTrace(contractStart, AtomicSwap.mkContract(scheme), [
+ playTrace(mkInitialMarloweState(contractStart, scheme), AtomicSwap.mkContract(scheme), [
{
tx_interval: aTxInterval,
tx_inputs: inputFlow,
@@ -305,16 +217,13 @@ describe("Atomic Swap", () => {
deadline: aDeadlineInThePast,
},
};
- const inputFlow = [
- (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
- (getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input,
- ];
+ const inputFlow = [(getApplicableActions(scheme, waitingForAnswer)[0] as Swap).input];
// Execute
const { contract, payments, warnings } = expectType(
G.TransactionSuccess,
- playTrace(contractStart, AtomicSwap.mkContract(scheme), [
+ playTrace(mkInitialMarloweState(contractStart, scheme), AtomicSwap.mkContract(scheme), [
{
tx_interval: aTxInterval,
tx_inputs: inputFlow,
@@ -378,16 +287,15 @@ describe("Atomic Swap", () => {
deadline: aDeadlineInTheFuture,
},
};
- const inputFlow = [(getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input];
// Execute
const { contract, payments, warnings } = expectType(
G.TransactionSuccess,
- playTrace(contractStart, AtomicSwap.mkContract(scheme), [
+ playTrace(mkInitialMarloweState(contractStart, scheme), AtomicSwap.mkContract(scheme), [
{
tx_interval: aTxInterval,
- tx_inputs: inputFlow,
+ tx_inputs: [],
},
])
);
@@ -406,10 +314,7 @@ describe("Atomic Swap", () => {
expect(contract).toBe(close);
expect(payments).toStrictEqual(expectedPayments);
- const state = getClosedState(
- scheme,
- inputFlow.map((input) => ({ interval: aTxInterval, input: input }))
- );
+ const state = getClosedState(scheme, []);
expect(state.reason.type).toBe("NotAnsweredOnTime");
});
it("when the seller has retracted - SellerRetracted", () => {
@@ -429,16 +334,13 @@ describe("Atomic Swap", () => {
deadline: aDeadlineInTheFuture,
},
};
- const inputFlow = [
- (getApplicableActions(scheme, waitingSellerOffer)[0] as ProvisionOffer).input,
- (getApplicableActions(scheme, waitingForAnswer)[1] as Retract).input,
- ];
+ const inputFlow = [(getApplicableActions(scheme, waitingForAnswer)[1] as Retract).input];
// Execute
const { contract, payments, warnings } = expectType(
G.TransactionSuccess,
- playTrace(contractStart, AtomicSwap.mkContract(scheme), [
+ playTrace(mkInitialMarloweState(contractStart, scheme), AtomicSwap.mkContract(scheme), [
{
tx_interval: aTxInterval,
tx_inputs: inputFlow,
@@ -466,49 +368,6 @@ describe("Atomic Swap", () => {
);
expect(state.reason.type).toBe("SellerRetracted");
});
- it("when the seller has not provisioned the contract on time and advance is applied - NoOfferProvisionnedOnTime", () => {
- // Set up
- const scheme: AtomicSwap.Scheme = {
- offer: {
- seller: { address: "sellerAddress" },
- deadline: aDeadlineInThePast,
- asset: anAsset,
- },
- ask: {
- buyer: { role_token: "buyer" },
- deadline: aDeadlineInTheFuture,
- asset: anotherAsset,
- },
- swapConfirmation: {
- deadline: aDeadlineInTheFuture,
- },
- };
- const advance: Input[] = [];
-
- // Execute
-
- const { contract, payments, warnings } = expectType(
- G.TransactionSuccess,
- playTrace(contractStart, AtomicSwap.mkContract(scheme), [
- {
- tx_interval: aTxInterval,
- tx_inputs: advance,
- },
- ])
- );
-
- // Verify
-
- expect(warnings).toStrictEqual([]);
- expect(contract).toBe(close);
- expect(payments).toStrictEqual([]);
-
- const state = getClosedState(
- scheme,
- advance.map((input) => ({ interval: aTxInterval, input: input }))
- );
- expect(state.reason.type).toBe("NoOfferProvisionnedOnTime");
- });
});
});
diff --git a/packages/language/specification-client/src/main.ts b/packages/language/specification-client/src/main.ts
index b2d630f6..e40c9849 100644
--- a/packages/language/specification-client/src/main.ts
+++ b/packages/language/specification-client/src/main.ts
@@ -7,7 +7,13 @@ import * as P from "@marlowe.io/language-core-v1/playground-v1";
import jsonBigInt from "json-bigint";
import { createJsonStream } from "./jsonStream.js";
import { Environment, MarloweState, Value } from "@marlowe.io/language-core-v1";
-import { evalValue, evalObservation, computeTransaction, playTrace } from "@marlowe.io/language-core-v1/semantics";
+import {
+ evalValue,
+ evalObservation,
+ computeTransaction,
+ playTrace,
+ emptyState,
+} from "@marlowe.io/language-core-v1/semantics";
// // We need to patch the JSON.stringify in order for BigInt serialization to work.
const { stringify, parse } = jsonBigInt({
useNativeBigInt: true,
@@ -147,7 +153,7 @@ function main() {
return requestResponse(computeTransaction(req.transactionInput, req.state, req.coreContract));
}
if (PlayTraceRequest.is(req)) {
- return requestResponse(playTrace(req.initialTime, req.coreContract, req.transactionInputs));
+ return requestResponse(playTrace(emptyState(req.initialTime), req.coreContract, req.transactionInputs));
}
return console.log("RequestNotImplemented");
},
diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts
index b0b41173..124c5cca 100644
--- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts
+++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts
@@ -35,6 +35,8 @@ import {
AddressBech32Guard,
ContractId,
ContractIdGuard,
+ accountDeposits,
+ accountDepositsGuard,
} from "@marlowe.io/runtime-core";
import { ContractHeader, ContractHeaderGuard } from "../header.js";
import { RolesConfiguration, RolesConfigurationGuard } from "../rolesConfigurations.js";
@@ -191,7 +193,7 @@ export type BuildCreateContractTxRequestWithContract = {
export const BuildCreateContractTxRequestOptionsGuard = assertGuardEqual(
proxy(),
t.intersection([
- t.type({ changeAddress: AddressBech32Guard, version: MarloweVersion }),
+ t.type({ changeAddress: AddressBech32Guard, version: MarloweVersion, accounts: accountDepositsGuard }),
t.partial({
roles: RolesConfigurationGuard,
threadRoleName: G.RoleName,
@@ -492,6 +494,12 @@ export interface BuildCreateContractTxRequestOptions {
*/
roles?: RolesConfiguration;
+ /**
+ * describe an initial deposit of assets for the accounts in the contract. The key is the address or role name and the value is the assets.
+ * These assets will be deposited in the accounts at the creation of the contract, and will come from the contract creator's wallet.
+ */
+ accounts: accountDeposits;
+
/**
* The Marlowe validator version to use.
*/
@@ -517,6 +525,7 @@ export const PostContractsRequest = t.intersection([
contract: ContractOrSourceIdGuard,
tags: TagsGuard,
metadata: MetadataGuard,
+ accounts: accountDepositsGuard,
}),
t.partial({ roles: RolesConfigurationGuard }),
t.partial({ threadTokenName: G.RoleName }),
diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts
index 2a99b80b..25732fb6 100644
--- a/packages/runtime/client/rest/src/index.ts
+++ b/packages/runtime/client/rest/src/index.ts
@@ -30,10 +30,7 @@ import { unsafeTaskEither } from "@marlowe.io/adapter/fp-ts";
import { ContractDetails } from "./contract/details.js";
import { TransactionDetails } from "./contract/transaction/details.js";
import { RuntimeStatus, healthcheck } from "./runtime/status.js";
-import {
- CompatibleRuntimeVersionGuard,
- RuntimeVersion,
-} from "./runtime/version.js";
+import { CompatibleRuntimeVersionGuard, RuntimeVersion } from "./runtime/version.js";
import { dynamicAssertType } from "@marlowe.io/adapter/io-ts";
export { Page, ItemRange, ItemRangeGuard, ItemRangeBrand, PageGuard } from "./pagination.js";
@@ -262,30 +259,22 @@ export function mkRestClient(baseURL: string, strict = true): RestClient {
version() {
return healthcheck(axiosInstance).then((status) => status.version);
},
- getContracts: withDynamicTypeCheck(
- strict,
- Contracts.GetContractsRequestGuard,
- (request) => {
- const range = request?.range;
- const tags = request?.tags ?? [];
- const partyAddresses = request?.partyAddresses ?? [];
- const partyRoles = request?.partyRoles ?? [];
- return unsafeTaskEither(
- Contracts.getHeadersByRangeViaAxios(axiosInstance)(range)({
- tags,
- partyAddresses,
- partyRoles,
- })
- );
- }
- ),
- getContractById: withDynamicTypeCheck(
- strict,
- Contract.GetContractByIdRequest,
- (request) => {
- return Contract.getContractById(axiosInstance, request.contractId);
- }
- ),
+ getContracts: withDynamicTypeCheck(strict, Contracts.GetContractsRequestGuard, (request) => {
+ const range = request?.range;
+ const tags = request?.tags ?? [];
+ const partyAddresses = request?.partyAddresses ?? [];
+ const partyRoles = request?.partyRoles ?? [];
+ return unsafeTaskEither(
+ Contracts.getHeadersByRangeViaAxios(axiosInstance)(range)({
+ tags,
+ partyAddresses,
+ partyRoles,
+ })
+ );
+ }),
+ getContractById: withDynamicTypeCheck(strict, Contract.GetContractByIdRequest, (request) => {
+ return Contract.getContractById(axiosInstance, request.contractId);
+ }),
buildCreateContractTx: withDynamicTypeCheck(
strict,
Contracts.BuildCreateContractTxRequestGuard,
@@ -294,9 +283,7 @@ export function mkRestClient(baseURL: string, strict = true): RestClient {
// NOTE: Runtime 0.0.5 requires an explicit minUTxODeposit, but 0.0.6 and forward allows that field as optional
// and it will calculate the actual minimum required. We use the version of the runtime to determine
// if we use a "safe" default that is bigger than needed.
- const minUTxODeposit =
- request.minimumLovelaceUTxODeposit ??
- (version === "0.0.5" ? 3000000 : undefined);
+ const minUTxODeposit = request.minimumLovelaceUTxODeposit ?? (version === "0.0.5" ? 3000000 : undefined);
const postContractsRequest = {
contract: "contract" in request ? request.contract : request.sourceId,
version: request.version,
@@ -305,6 +292,7 @@ export function mkRestClient(baseURL: string, strict = true): RestClient {
minUTxODeposit,
roles: request.roles,
threadRoleName: request.threadRoleName,
+ accounts: request.accounts ?? {},
};
const addressesAndCollaterals = {
changeAddress: request.changeAddress,
@@ -312,31 +300,19 @@ export function mkRestClient(baseURL: string, strict = true): RestClient {
collateralUTxOs: request.collateralUTxOs ?? [],
};
return unsafeTaskEither(
- Contracts.postViaAxios(axiosInstance)(
- postContractsRequest,
- addressesAndCollaterals,
- request.stakeAddress
- )
+ Contracts.postViaAxios(axiosInstance)(postContractsRequest, addressesAndCollaterals, request.stakeAddress)
);
}
),
- createContractSources: withDynamicTypeCheck(
- strict,
- Sources.CreateContractSourcesRequestGuard,
- (request) => {
- const {
- bundle: { main, bundle },
- } = request;
- return Sources.createContractSources(axiosInstance)(main, bundle);
- }
- ),
- getContractSourceById: withDynamicTypeCheck(
- strict,
- Sources.GetContractBySourceIdRequestGuard,
- (request) => {
- return Sources.getContractSourceById(axiosInstance)(request);
- }
- ),
+ createContractSources: withDynamicTypeCheck(strict, Sources.CreateContractSourcesRequestGuard, (request) => {
+ const {
+ bundle: { main, bundle },
+ } = request;
+ return Sources.createContractSources(axiosInstance)(main, bundle);
+ }),
+ getContractSourceById: withDynamicTypeCheck(strict, Sources.GetContractBySourceIdRequestGuard, (request) => {
+ return Sources.getContractSourceById(axiosInstance)(request);
+ }),
getContractSourceAdjacency: withDynamicTypeCheck(
strict,
Sources.GetContractSourceAdjacencyRequestGuard,
@@ -344,39 +320,22 @@ export function mkRestClient(baseURL: string, strict = true): RestClient {
return Sources.getContractSourceAdjacency(axiosInstance)(request);
}
),
- getContractSourceClosure: withDynamicTypeCheck(
- strict,
- Sources.GetContractSourceClosureRequestGuard,
- (request) => {
- return Sources.getContractSourceClosure(axiosInstance)(request);
- }
- ),
- getNextStepsForContract: withDynamicTypeCheck(
- strict,
- Next.GetNextStepsForContractRequestGuard,
- (request) => {
- return Next.getNextStepsForContract(axiosInstance)(request);
- }
- ),
- submitContract: withDynamicTypeCheck(
- strict,
- Contract.SubmitContractRequestGuard,
- (request) => {
- const { contractId, txEnvelope } = request;
- return Contract.submitContract(axiosInstance)(contractId, txEnvelope);
- }
- ),
+ getContractSourceClosure: withDynamicTypeCheck(strict, Sources.GetContractSourceClosureRequestGuard, (request) => {
+ return Sources.getContractSourceClosure(axiosInstance)(request);
+ }),
+ getNextStepsForContract: withDynamicTypeCheck(strict, Next.GetNextStepsForContractRequestGuard, (request) => {
+ return Next.getNextStepsForContract(axiosInstance)(request);
+ }),
+ submitContract: withDynamicTypeCheck(strict, Contract.SubmitContractRequestGuard, (request) => {
+ const { contractId, txEnvelope } = request;
+ return Contract.submitContract(axiosInstance)(contractId, txEnvelope);
+ }),
getTransactionsForContract: withDynamicTypeCheck(
strict,
Transactions.GetTransactionsForContractRequestGuard,
(request) => {
const { contractId, range } = request;
- return unsafeTaskEither(
- Transactions.getHeadersByRangeViaAxios(axiosInstance)(
- contractId,
- range
- )
- );
+ return unsafeTaskEither(Transactions.getHeadersByRangeViaAxios(axiosInstance)(contractId, range));
}
),
submitContractTransaction: withDynamicTypeCheck(
@@ -385,11 +344,7 @@ export function mkRestClient(baseURL: string, strict = true): RestClient {
(request) => {
const { contractId, transactionId, hexTransactionWitnessSet } = request;
return unsafeTaskEither(
- Transaction.putViaAxios(axiosInstance)(
- contractId,
- transactionId,
- hexTransactionWitnessSet
- )
+ Transaction.putViaAxios(axiosInstance)(contractId, transactionId, hexTransactionWitnessSet)
);
}
),
@@ -398,123 +353,74 @@ export function mkRestClient(baseURL: string, strict = true): RestClient {
Transaction.GetContractTransactionByIdRequestGuard,
(request) => {
const { contractId, txId } = request;
- return unsafeTaskEither(
- Transaction.getViaAxios(axiosInstance)(contractId, txId)
- );
+ return unsafeTaskEither(Transaction.getViaAxios(axiosInstance)(contractId, txId));
}
),
- withdrawPayouts: withDynamicTypeCheck(
- strict,
- Withdrawals.WithdrawPayoutsRequestGuard,
- (request) => {
- const { payoutIds, changeAddress } = request;
- return unsafeTaskEither(
- Withdrawals.postViaAxios(axiosInstance)(payoutIds, {
+ withdrawPayouts: withDynamicTypeCheck(strict, Withdrawals.WithdrawPayoutsRequestGuard, (request) => {
+ const { payoutIds, changeAddress } = request;
+ return unsafeTaskEither(
+ Withdrawals.postViaAxios(axiosInstance)(payoutIds, {
+ changeAddress,
+ usedAddresses: request.usedAddresses ?? [],
+ collateralUTxOs: request.collateralUTxOs ?? [],
+ })
+ );
+ }),
+ getWithdrawalById: withDynamicTypeCheck(strict, Withdrawal.GetWithdrawalByIdRequestGuard, async (request) => {
+ const { withdrawalId } = request;
+ const { block, ...response } = await unsafeTaskEither(Withdrawal.getViaAxios(axiosInstance)(withdrawalId));
+ return { ...response, block: O.toUndefined(block) };
+ }),
+ getWithdrawals: withDynamicTypeCheck(strict, Withdrawals.GetWithdrawalsRequestGuard, (request) => {
+ return unsafeTaskEither(Withdrawals.getHeadersByRangeViaAxios(axiosInstance)(request));
+ }),
+ applyInputsToContract: withDynamicTypeCheck(strict, Transactions.ApplyInputsToContractRequestGuard, (request) => {
+ const { contractId, changeAddress, invalidBefore, invalidHereafter, inputs } = request;
+ return unsafeTaskEither(
+ Transactions.postViaAxios(axiosInstance)(
+ contractId,
+ {
+ invalidBefore,
+ invalidHereafter,
+ version: request.version ?? "v1",
+ metadata: request.metadata ?? {},
+ tags: request.tags ?? {},
+ inputs,
+ },
+ {
changeAddress,
usedAddresses: request.usedAddresses ?? [],
collateralUTxOs: request.collateralUTxOs ?? [],
- })
- );
- }
- ),
- getWithdrawalById: withDynamicTypeCheck(
- strict,
- Withdrawal.GetWithdrawalByIdRequestGuard,
- async (request) => {
- const { withdrawalId } = request;
- const { block, ...response } = await unsafeTaskEither(
- Withdrawal.getViaAxios(axiosInstance)(withdrawalId)
- );
- return { ...response, block: O.toUndefined(block) };
- }
- ),
- getWithdrawals: withDynamicTypeCheck(
- strict,
- Withdrawals.GetWithdrawalsRequestGuard,
- (request) => {
- return unsafeTaskEither(
- Withdrawals.getHeadersByRangeViaAxios(axiosInstance)(request)
- );
- }
- ),
- applyInputsToContract: withDynamicTypeCheck(
- strict,
- Transactions.ApplyInputsToContractRequestGuard,
- (request) => {
- const {
- contractId,
- changeAddress,
- invalidBefore,
- invalidHereafter,
- inputs,
- } = request;
- return unsafeTaskEither(
- Transactions.postViaAxios(axiosInstance)(
- contractId,
- {
- invalidBefore,
- invalidHereafter,
- version: request.version ?? "v1",
- metadata: request.metadata ?? {},
- tags: request.tags ?? {},
- inputs,
- },
- {
- changeAddress,
- usedAddresses: request.usedAddresses ?? [],
- collateralUTxOs: request.collateralUTxOs ?? [],
- }
- )
- );
- }
- ),
- submitWithdrawal: withDynamicTypeCheck(
- strict,
- Withdrawal.SubmitWithdrawalRequestGuard,
- (request) => {
- const { withdrawalId, hexTransactionWitnessSet } = request;
- return unsafeTaskEither(
- Withdrawal.putViaAxios(axiosInstance)(
- withdrawalId,
- hexTransactionWitnessSet
- )
- );
- }
- ),
- getPayouts: withDynamicTypeCheck(
- strict,
- Payouts.GetPayoutsRequestGuard,
- async (request) => {
- const { contractIds, roleTokens, range, status } = request;
- return await unsafeTaskEither(
- Payouts.getHeadersByRangeViaAxios(axiosInstance)(range)(contractIds)(
- roleTokens
- )(O.fromNullable(status))
- );
- }
- ),
- getPayoutById: withDynamicTypeCheck(
- strict,
- Payout.GetPayoutByIdRequestGuard,
- async (request) => {
- const { payoutId } = request;
- const result = await unsafeTaskEither(
- Payout.getViaAxios(axiosInstance)(payoutId)
- );
- return {
- payoutId: result.payoutId,
- contractId: result.contractId,
- ...O.match(
- () => ({}),
- (withdrawalId) => ({ withdrawalId })
- )(result.withdrawalId),
- role: result.role,
- payoutValidatorAddress: result.payoutValidatorAddress,
- status: result.status,
- assets: result.assets,
- };
- }
- ),
+ }
+ )
+ );
+ }),
+ submitWithdrawal: withDynamicTypeCheck(strict, Withdrawal.SubmitWithdrawalRequestGuard, (request) => {
+ const { withdrawalId, hexTransactionWitnessSet } = request;
+ return unsafeTaskEither(Withdrawal.putViaAxios(axiosInstance)(withdrawalId, hexTransactionWitnessSet));
+ }),
+ getPayouts: withDynamicTypeCheck(strict, Payouts.GetPayoutsRequestGuard, async (request) => {
+ const { contractIds, roleTokens, range, status } = request;
+ return await unsafeTaskEither(
+ Payouts.getHeadersByRangeViaAxios(axiosInstance)(range)(contractIds)(roleTokens)(O.fromNullable(status))
+ );
+ }),
+ getPayoutById: withDynamicTypeCheck(strict, Payout.GetPayoutByIdRequestGuard, async (request) => {
+ const { payoutId } = request;
+ const result = await unsafeTaskEither(Payout.getViaAxios(axiosInstance)(payoutId));
+ return {
+ payoutId: result.payoutId,
+ contractId: result.contractId,
+ ...O.match(
+ () => ({}),
+ (withdrawalId) => ({ withdrawalId })
+ )(result.withdrawalId),
+ role: result.role,
+ payoutValidatorAddress: result.payoutValidatorAddress,
+ status: result.status,
+ assets: result.assets,
+ };
+ }),
};
}
diff --git a/packages/runtime/client/rest/src/runtime/version.ts b/packages/runtime/client/rest/src/runtime/version.ts
index 6b1cec4c..ccf4e186 100644
--- a/packages/runtime/client/rest/src/runtime/version.ts
+++ b/packages/runtime/client/rest/src/runtime/version.ts
@@ -16,9 +16,10 @@ export type RuntimeVersion = t.TypeOf;
export const runtimeVersion = (s: string) => unsafeEither(RuntimeVersionGuard.decode(s));
-export type CompatibleRuntimeVersion = "0.0.6" | "0.0.5";
+export type CompatibleRuntimeVersion = "1.0.0" | "0.0.6" | "0.0.5";
export const CompatibleRuntimeVersionGuard: t.Type = t.union([
+ t.literal("1.0.0"),
t.literal("0.0.6"),
t.literal("0.0.5"),
]);
diff --git a/packages/runtime/core/src/asset/index.ts b/packages/runtime/core/src/asset/index.ts
index 1d224f6b..5306c0a1 100644
--- a/packages/runtime/core/src/asset/index.ts
+++ b/packages/runtime/core/src/asset/index.ts
@@ -3,6 +3,7 @@ import { PolicyId, PolicyIdGuard, policyId } from "../policyId.js";
import * as Marlowe from "@marlowe.io/language-core-v1";
import * as G from "@marlowe.io/language-core-v1/guards";
+import { assertGuardEqual, proxy, convertNullableToUndefined } from "@marlowe.io/adapter/io-ts";
export type AssetName = t.TypeOf;
export const AssetName = t.string;
@@ -37,11 +38,25 @@ export const token =
export const lovelaces = (quantity: AssetQuantity): Token => token(quantity)(assetId(policyId(""))(""));
-export type Tokens = t.TypeOf;
-export const Tokens = t.array(Token);
-
-export type Assets = t.TypeOf;
-export const Assets = t.type({ lovelaces: AssetQuantityGuard, tokens: Tokens });
+export type Tokens = Token[];
+export const TokensGuard = assertGuardEqual(proxy(), t.array(Token));
+
+export type Assets = {
+ lovelaces?: AssetQuantity;
+ tokens: Tokens;
+};
+
+export const AssetsGuard = assertGuardEqual(
+ proxy(),
+ t.intersection([
+ t.type({
+ tokens: TokensGuard,
+ }),
+ t.partial({
+ lovelaces: convertNullableToUndefined(AssetQuantityGuard),
+ }),
+ ])
+);
export const assetIdToString: (assetId: AssetId) => string = (assetId) => `${assetId.policyId}|${assetId.assetName}`;
@@ -67,6 +82,23 @@ export const unMapAsset: (assets: AssetsMap) => Assets = (restAssets) => ({
tokens: unMapTokens(restAssets.tokens),
});
+export const mapAsset: (assets: Assets) => AssetsMap = (assets) => ({
+ lovelace: assets.lovelaces ?? 0n,
+ tokens: mapTokens(assets.tokens),
+});
+
+export const mapTokens: (tokens: Tokens) => TokensMap = (tokens) =>
+ tokens.reduce((acc, token) => {
+ const policyId = token.assetId.policyId;
+ const assetName = token.assetId.assetName;
+ const quantity = token.quantity;
+ if (acc[policyId] === undefined) {
+ acc[policyId] = {};
+ }
+ acc[policyId][assetName] = quantity;
+ return acc;
+ }, {} as TokensMap);
+
export const unMapTokens: (tokens: TokensMap) => Tokens = (restTokens) =>
Object.entries(restTokens)
.map(([aPolicyId, x]) =>
diff --git a/packages/runtime/core/src/contract/accountDeposits.ts b/packages/runtime/core/src/contract/accountDeposits.ts
new file mode 100644
index 00000000..cf8f3c77
--- /dev/null
+++ b/packages/runtime/core/src/contract/accountDeposits.ts
@@ -0,0 +1,23 @@
+import * as t from "io-ts/lib/index.js";
+import { Party, partyToString } from "@marlowe.io/language-core-v1";
+import { Assets, AssetsMap, AssetsMapGuard, mapAsset } from "../asset/index.js";
+
+export type AddressOrRole = string;
+export const AddressOrRoleGuard = t.string;
+/**
+ * A map of tags to their content. The key is a string, the value can be anything.
+ */
+export type accountDeposits = { [key in AddressOrRole]: AssetsMap };
+/**
+ * @hidden
+ */
+export const accountDepositsGuard = t.record(AddressOrRoleGuard, AssetsMapGuard);
+/**
+ * a function that takes a list of party with associated assets and returns an accountDeposits
+ * the party object is converted to a string using partyToString.
+ * @param accounts
+ * @returns
+ */
+export function mkaccountDeposits(accounts: [Party, Assets][]): accountDeposits {
+ return Object.fromEntries(accounts.map(([party, assets]) => [partyToString(party), mapAsset(assets)]));
+}
diff --git a/packages/runtime/core/src/index.ts b/packages/runtime/core/src/index.ts
index 60eb9e60..bf861c2b 100644
--- a/packages/runtime/core/src/index.ts
+++ b/packages/runtime/core/src/index.ts
@@ -6,6 +6,7 @@ export * from "./tx/index.js";
export * from "./metadata.js";
export * from "./tag.js";
export * from "./contract/id.js";
+export * from "./contract/accountDeposits.js";
export * from "./sourceId.js";
export * from "./asset/index.js";
export * from "./payout/index.js";
diff --git a/packages/runtime/core/src/payout/index.ts b/packages/runtime/core/src/payout/index.ts
index 11507002..15135fc9 100644
--- a/packages/runtime/core/src/payout/index.ts
+++ b/packages/runtime/core/src/payout/index.ts
@@ -6,7 +6,7 @@ import { pipe } from "fp-ts/lib/function.js";
import { head } from "fp-ts/lib/ReadonlyNonEmptyArray.js";
import { txId, TxId } from "../tx/id.js";
import { ContractIdGuard } from "../contract/id.js";
-import { AssetId, Assets } from "../asset/index.js";
+import { AssetId, Assets, AssetsGuard } from "../asset/index.js";
// QUESTION: @N.H: What is the difference between PayoutId and WithdrawalId?
export type PayoutId = Newtype<{ readonly ContractId: unique symbol }, string>;
@@ -31,7 +31,7 @@ export const PayoutAvailable = t.type({
payoutId: PayoutId,
contractId: ContractIdGuard,
role: AssetId,
- assets: Assets,
+ assets: AssetsGuard,
});
export type PayoutWithdrawn = t.TypeOf;
@@ -40,5 +40,5 @@ export const PayoutWithdrawn = t.type({
payoutId: PayoutId,
contractId: ContractIdGuard,
role: AssetId,
- assets: Assets,
+ assets: AssetsGuard,
});
diff --git a/packages/runtime/lifecycle/src/generic/applicable-actions.ts b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
index f4b23900..770931f7 100644
--- a/packages/runtime/lifecycle/src/generic/applicable-actions.ts
+++ b/packages/runtime/lifecycle/src/generic/applicable-actions.ts
@@ -85,6 +85,12 @@ export interface ApplicableActionsAPI {
*/
simulateInput(contractDetails: ActiveContract, input: ApplicableInput): TransactionSuccess;
+ /**
+ * Computes the environment for a contract. The environment is computed using the runtime tip as a lower bound and the next timeout
+ * as an upper bound.
+ */
+ computeEnvironment: (contract: Contract) => Promise;
+
/**
* Creates a filter function for the {@link ApplicableAction | applicable actions} of the wallet owner.
* The wallet is configured when we instantiate the {@link RuntimeLifecycle}. This function returns a new
@@ -140,6 +146,7 @@ export function mkApplicableActionsAPI(di: RestDI & WalletDI & GetContinuationDI
getInput: getApplicableInput(di),
simulateInput: simulateApplicableInput,
getApplicableActions: getApplicableActions(di),
+ computeEnvironment: (contract) => computeEnvironment(di, contract),
applyInput: applyInput(applyInputs(di)),
mkFilter,
};
@@ -158,7 +165,7 @@ function applyInput(doApply: ApplyInputDI) {
// way into the future and the time to slot conversion is undefined if the
// end time passes a certain threshold.
// We currently don't have the network parameters to do the conversion ourselves
- // so we leave to the runtime to calculate an adecuate max slot.
+ // so we leave to the runtime to calculate an adequate max slot.
// This might cause some issues if the contract relies on the TimeIntervalEnd value
// as the result of simulating and applying the input might differ.
// invalidHereafter: posixTimeToIso8601(
diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts
index 8de79ee5..4ac357bf 100644
--- a/packages/runtime/lifecycle/src/generic/contracts.ts
+++ b/packages/runtime/lifecycle/src/generic/contracts.ts
@@ -13,6 +13,7 @@ import {
StakeAddressBech32,
Metadata,
Tags,
+ accountDeposits,
} from "@marlowe.io/runtime-core";
import { FPTSRestAPI, RestClient, RestDI, ItemRange, DeprecatedRestDI } from "@marlowe.io/runtime-rest-client";
@@ -83,6 +84,19 @@ export interface CreateContractRequestBase {
*/
threadRoleName?: RoleName;
+ /**
+ * Initial Accounts State for the contract creation.
+ *
Properties
+ *
+ * The initial accounts state is a mapping of addresses or role tokens to their initial assets.
+ * The creator of the contract can define the initial assets for each participant.
+ * Assets will be withdrawn from the creator's wallet and deposited into the participant's accounts when the contract is created.
+ *
+ * @see
+ * - {@link @marlowe.io/runtime-core!accountDeposits}
+ */
+
+ accountDeposits?: accountDeposits;
/**
* Role Token Configuration for the new contract passed in the `contract` field.
*
@@ -362,7 +376,7 @@ export const createContract =
const baseRequest: BuildCreateContractTxRequestOptions = {
version: "v1",
-
+ accounts: createContractRequest.accountDeposits ?? {},
changeAddress: addressesAndCollaterals.changeAddress,
usedAddresses: addressesAndCollaterals.usedAddresses,
collateralUTxOs: addressesAndCollaterals.collateralUTxOs,
@@ -463,6 +477,7 @@ export const applyInputs =
metadata: applyInputsRequest.metadata,
tags: applyInputsRequest.tags,
});
+
const signed = await wallet.signTx(envelope.tx.cborHex);
await restClient.submitContractTransaction({
contractId,
diff --git a/packages/runtime/lifecycle/src/generic/new-contract-api.ts b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
index 784a2aa8..073a6408 100644
--- a/packages/runtime/lifecycle/src/generic/new-contract-api.ts
+++ b/packages/runtime/lifecycle/src/generic/new-contract-api.ts
@@ -1,11 +1,5 @@
import { ContractId, PolicyId, TxId, contractIdToTxId } from "@marlowe.io/runtime-core";
-import {
- ApplyInputsRequest,
- CreateContractRequest,
- createContract,
- applyInputs,
- getInputHistory,
-} from "./contracts.js";
+import { CreateContractRequest, createContract, getInputHistory } from "./contracts.js";
import { SingleInputTx, TransactionSuccess } from "@marlowe.io/language-core-v1/semantics";
import { ChosenNum, Contract, Environment, MarloweState } from "@marlowe.io/language-core-v1";
import { RestDI } from "@marlowe.io/runtime-rest-client";
@@ -22,7 +16,6 @@ import {
GetContinuationDI,
} from "./applicable-actions.js";
import * as Applicable from "./applicable-actions.js";
-import { empty } from "fp-ts/lib/ReadonlyRecord.js";
/**
*
@@ -225,12 +218,18 @@ function mkContractInstanceAPI(di: ContractsDI & GetContinuationDI & ChainTipDI,
return {
id,
waitForConfirmation: async () => {
- // Todo : This is a temporary solution. We need to implement a better way to get the last transaction.
- const txs = await di.restClient.getTransactionsForContract({ contractId: id }).then((res) => res.transactions);
- if (txs.length === 0) {
+ try {
+ // Todo : This is a temporary solution. We need to implement a better way to get the last transaction.
+ // Improve Error handling
+ const txs = await di.restClient.getTransactionsForContract({ contractId: id }).then((res) => res.transactions);
+ if (txs.length === 0) {
+ return di.wallet.waitConfirmation(contractCreationTxId);
+ } else {
+ return di.wallet.waitConfirmation(txs[txs.length - 1].transactionId);
+ }
+ } catch (e) {
+ // triggered when the contract is not found yet
return di.wallet.waitConfirmation(contractCreationTxId);
- } else {
- return di.wallet.waitConfirmation(txs[txs.length - 1].transactionId);
}
},
getDetails: async () => {
diff --git a/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts b/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts
index 137b3b95..b893b5ae 100644
--- a/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts
+++ b/packages/runtime/lifecycle/test/examples/swap.ada.token.e2e.spec.ts
@@ -4,7 +4,7 @@ import { addDays } from "date-fns";
import { datetoTimeout } from "@marlowe.io/language-core-v1";
import console from "console";
-import { runtimeTokenToMarloweTokenValue } from "@marlowe.io/runtime-core";
+import { mkaccountDeposits, runtimeTokenToMarloweTokenValue } from "@marlowe.io/runtime-core";
import { MINUTES } from "@marlowe.io/adapter/time";
import { AtomicSwap } from "@marlowe.io/language-examples";
import {
@@ -18,9 +18,18 @@ import {
} from "@marlowe.io/testing-kit";
import { AxiosError } from "axios";
import { MarloweJSON } from "@marlowe.io/adapter/codec";
-import { CanDeposit, onlyByContractIds } from "@marlowe.io/runtime-lifecycle/api";
+import {
+ CanDeposit,
+ CanNotify,
+ ContractInstanceAPI,
+ CreateContractRequest,
+ CreateContractRequestFromContract,
+ RuntimeLifecycle,
+ onlyByContractIds,
+} from "@marlowe.io/runtime-lifecycle/api";
import { mintRole } from "@marlowe.io/runtime-rest-client/contract";
+import { computeTransaction, emptyState, reduceContractUntilQuiescent } from "@marlowe.io/language-core-v1/semantics";
global.console = console;
@@ -80,32 +89,25 @@ describe("swap", () => {
const sellerContractInstance = await sellerLifecycle.newContractAPI.create({
contract: swapContract,
roles: { [scheme.ask.buyer.role_token]: mintRole("OpenRole") },
+ accountDeposits: mkaccountDeposits([[scheme.offer.seller, seller.assetsProvisioned]]),
});
+
sellerContractInstance.waitForConfirmation();
await seller.wallet.waitRuntimeSyncingTillCurrentWalletTip(sellerLifecycle.restClient);
expect(await sellerContractInstance.isActive()).toBeTruthy();
logInfo(`contract created : ${sellerContractInstance.id}`);
- logInfo(`Seller > Provision Offer`);
-
- let applicableActions = await sellerContractInstance.evaluateApplicableActions();
-
- expect(applicableActions.myActions.map((a) => a.type)).toBe(["Deposit"]);
- const provisionOffer = await applicableActions.toInput(applicableActions.myActions[0] as CanDeposit);
-
- await sellerContractInstance.applyInput({ input: provisionOffer });
-
- await sellerContractInstance.waitForConfirmation();
- await seller.wallet.waitRuntimeSyncingTillCurrentWalletTip(sellerLifecycle.restClient);
-
logInfo(`Buyer > Swap`);
const buyerContractInstance = await buyerLifecycle.newContractAPI.load(sellerContractInstance.id);
- applicableActions = await buyerContractInstance.evaluateApplicableActions();
- expect(applicableActions.myActions.map((a) => a.type)).toBe(["Deposit"]);
+ let applicableActions = await buyerContractInstance.evaluateApplicableActions();
+ // N.B : the applicable actions' API is not able yet to handle Open Roles in the contract properly
+ // so myactions will be empty in that case and we need to use actions instead and manually select
+ // the action Desposit we want to apply.
+ expect(applicableActions.actions.map((a) => a.type)).toStrictEqual(["Deposit", "Choice"]);
- const swap = await applicableActions.toInput(applicableActions.myActions[0] as CanDeposit);
+ const swap = await applicableActions.toInput(applicableActions.actions[0] as CanDeposit);
await buyerContractInstance.applyInput({ input: swap });
@@ -116,9 +118,10 @@ describe("swap", () => {
const anyoneContractInstance = await anyoneLifecycle.newContractAPI.load(sellerContractInstance.id);
applicableActions = await buyerContractInstance.evaluateApplicableActions();
- expect(applicableActions.myActions.map((a) => a.type)).toBe(["Notify"]);
+ expect(applicableActions.myActions.map((a) => a.type)).toStrictEqual(["Notify"]);
+ const swapConfirmation = await applicableActions.toInput(applicableActions.myActions[0] as CanNotify);
- await anyoneContractInstance.applyInput({ input: swap });
+ await anyoneContractInstance.applyInput({ input: swapConfirmation });
await anyoneContractInstance.waitForConfirmation();
await buyer.wallet.waitRuntimeSyncingTillCurrentWalletTip(sellerLifecycle.restClient);
@@ -128,7 +131,7 @@ describe("swap", () => {
logInfo(`Buyer > Retrieve Payout`);
const buyerPayouts = await buyerLifecycle.payouts.available(onlyByContractIds([buyerContractInstance.id]));
- expect(buyerPayouts.length).toBe(1);
+ expect(buyerPayouts.length).toStrictEqual(1);
await buyerLifecycle.payouts.withdraw([buyerPayouts[0].payoutId]);
logInfo(`Swapped Completed`);
diff --git a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts
index 497d6805..39185e71 100644
--- a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts
+++ b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts
@@ -7,6 +7,12 @@ import { oneNotifyTrue } from "@marlowe.io/language-examples";
import console from "console";
import { MINUTES } from "@marlowe.io/adapter/time";
import { logError, logInfo, mkTestEnvironment, readTestConfiguration } from "@marlowe.io/testing-kit";
+import {
+ CanAdvance,
+ CanNotify,
+ ContractInstanceAPI,
+ CreateContractRequest,
+} from "@marlowe.io/runtime-lifecycle/api.js";
global.console = console;
@@ -16,13 +22,26 @@ describe("Runtime Contract Lifecycle ", () => {
async () => {
try {
const { bank, mkLifecycle } = await readTestConfiguration().then(mkTestEnvironment({}));
- const runtimeLifecycle = mkLifecycle(bank);
- const contract = await runtimeLifecycle.newContractAPI.create({ contract: close });
- await contract.waitForConfirmation();
- logInfo(`contract created : ${contract.id}`);
- expect(await contract.isClosed()).toBeTruthy();
+ const runtime = mkLifecycle(bank);
+ const contractInstance = await runtime.newContractAPI.create({ contract: close });
+ await contractInstance.waitForConfirmation();
+ await bank.waitRuntimeSyncingTillCurrentWalletTip(runtime.restClient);
+
+ logInfo(`contract created : ${contractInstance.id}`);
+ // N.B : This particular Close contract needs to be reduced/advanced to be closed
+ expect(await contractInstance.isActive()).toBeTruthy();
+ let applicableActions = await contractInstance.evaluateApplicableActions();
+
+ expect(applicableActions.myActions.map((a) => a.type)).toStrictEqual(["Advance"]);
+ const inputAdvance = await applicableActions.toInput(applicableActions.myActions[0] as CanAdvance);
+
+ await contractInstance.applyInput({ input: inputAdvance });
+ await contractInstance.waitForConfirmation();
+ await bank.waitRuntimeSyncingTillCurrentWalletTip(runtime.restClient);
+ expect(await contractInstance.isClosed()).toBeTruthy();
} catch (e) {
const error = e as AxiosError;
+ logError("An error occurred while creating a contract");
logError(JSON.stringify(error.response?.data));
logError(JSON.stringify(error));
expect(true).toBe(false);
@@ -41,12 +60,18 @@ describe("Runtime Contract Lifecycle ", () => {
const notifyTimeout = pipe(addDays(Date.now(), 1), datetoTimeout);
const contractInstance = await runtime.newContractAPI.create({ contract: oneNotifyTrue(notifyTimeout) });
await contractInstance.waitForConfirmation();
+ await bank.waitRuntimeSyncingTillCurrentWalletTip(runtime.restClient);
logInfo(`contract created : ${contractInstance.id}`);
expect(await contractInstance.isActive()).toBeTruthy();
- await bank.waitRuntimeSyncingTillCurrentWalletTip(runtime.restClient);
- await contractInstance.applyInputs({ inputs: [inputNotify] });
+ let applicableActions = await contractInstance.evaluateApplicableActions();
+
+ expect(applicableActions.myActions.map((a) => a.type)).toStrictEqual(["Notify"]);
+ const inputNotify = await applicableActions.toInput(applicableActions.myActions[0] as CanNotify);
+
+ await contractInstance.applyInput({ input: inputNotify });
await contractInstance.waitForConfirmation();
+ await bank.waitRuntimeSyncingTillCurrentWalletTip(runtime.restClient);
expect(await contractInstance.isClosed()).toBeTruthy();
} catch (e) {
diff --git a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts
deleted file mode 100644
index 4e6fdd06..00000000
--- a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { pipe } from "fp-ts/lib/function.js";
-import { addDays } from "date-fns";
-
-import { AtomicSwap } from "@marlowe.io/language-examples";
-import { datetoTimeout, adaValue } from "@marlowe.io/language-core-v1";
-import { Deposit } from "@marlowe.io/language-core-v1/next";
-
-import console from "console";
-import { runtimeTokenToMarloweTokenValue } from "@marlowe.io/runtime-core";
-import { onlyByContractIds } from "@marlowe.io/runtime-lifecycle/api";
-import { MINUTES } from "@marlowe.io/adapter/time";
-import { mintRole } from "@marlowe.io/runtime-rest-client/contract";
-
-global.console = console;
-
-describe.skip("Payouts", () => {
- // const provisionScheme = {
- // provider: { adaAmount: 20_000_000n },
- // swapper: { adaAmount: 20_000_000n, tokenAmount: 50n, tokenName: "TokenA" },
- // };
-
- // async function executeSwapWithRequiredWithdrawalTillClosing() {
- // const { tokenValueMinted, runtime, adaProvider, tokenProvider } =
- // await provisionAnAdaAndTokenProvider(
- // getMarloweRuntimeUrl(),
- // getBlockfrostContext(),
- // getBankPrivateKey(),
- // provisionScheme
- // );
- // const scheme: AtomicSwap.Scheme = {
- // offer: {
- // seller: { address: adaProvider.address },
- // deadline: pipe(addDays(Date.now(), 1), datetoTimeout),
- // asset: adaValue(2n),
- // },
- // ask: {
- // buyer: { role_token: "buyer" },
- // deadline: pipe(addDays(Date.now(), 1), datetoTimeout),
- // asset: runtimeTokenToMarloweTokenValue(tokenValueMinted),
- // },
- // swapConfirmation: {
- // deadline: pipe(addDays(Date.now(), 1), datetoTimeout),
- // },
- // };
-
- // const swapContract = AtomicSwap.mkContract(scheme);
- // const [contractId, txCreatedContract] = await runtime(
- // adaProvider
- // ).contracts.createContract({
- // contract: swapContract,
- // roles: { [scheme.ask.buyer.role_token]: mintRole(tokenProvider.address) },
- // });
-
- // await runtime(adaProvider).wallet.waitConfirmation(txCreatedContract);
-
- // // Applying the first Deposit
- // let next = await runtime(adaProvider).contracts.getApplicableInputs(
- // contractId
- // );
- // const txFirstTokensDeposited = await runtime(
- // adaProvider
- // ).contracts.applyInputs(contractId, {
- // inputs: [pipe(next.applicable_inputs.deposits[0], Deposit.toInput)],
- // });
- // await runtime(adaProvider).wallet.waitConfirmation(txFirstTokensDeposited);
-
- // // Applying the second Deposit
- // next = await runtime(tokenProvider).contracts.getApplicableInputs(
- // contractId
- // );
- // await runtime(tokenProvider).contracts.applyInputs(contractId, {
- // inputs: [pipe(next.applicable_inputs.deposits[0], Deposit.toInput)],
- // });
- // await runtime(tokenProvider).wallet.waitConfirmation(
- // txFirstTokensDeposited
- // );
-
- // return {
- // contractId,
- // runtime,
- // adaProvider,
- // tokenProvider,
- // };
- // }
-
- it(
- "Payouts can be withdrawn",
- async () => {
- // const result = await executeSwapWithRequiredWithdrawalTillClosing();
- // const { adaProvider, tokenProvider, contractId, runtime } = result;
- // const adaProviderPayouts = await runtime(adaProvider).payouts.available(
- // onlyByContractIds([contractId])
- // );
- // expect(adaProviderPayouts.length).toBe(1);
- // await runtime(adaProvider).payouts.withdraw([
- // adaProviderPayouts[0].payoutId,
- // ]);
- // const tokenProviderPayouts = await runtime(
- // tokenProvider
- // ).payouts.available(onlyByContractIds([contractId]));
- // expect(tokenProviderPayouts.length).toBe(1);
- // await runtime(tokenProvider).payouts.withdraw([
- // tokenProviderPayouts[0].payoutId,
- // ]);
- },
- 10 * MINUTES
- );
-});
diff --git a/packages/testing-kit/src/environment/index.ts b/packages/testing-kit/src/environment/index.ts
index f51c3ab3..8109c3dc 100644
--- a/packages/testing-kit/src/environment/index.ts
+++ b/packages/testing-kit/src/environment/index.ts
@@ -5,7 +5,14 @@ import { Lucid, Blockfrost } from "lucid-cardano";
import { RuntimeLifecycle } from "@marlowe.io/runtime-lifecycle/api";
import { Assets } from "@marlowe.io/runtime-core";
-import { TestConfiguration, logInfo, logWalletInfo, mkLucidWalletTest } from "@marlowe.io/testing-kit";
+import {
+ TestConfiguration,
+ logDebug,
+ logInfo,
+ logWalletInfo,
+ logWarning,
+ mkLucidWalletTest,
+} from "@marlowe.io/testing-kit";
import { ProvisionRequest, WalletTestAPI } from "../wallet/api.js";
/**
@@ -68,8 +75,9 @@ export const mkTestEnvironment =
if (bankBalance <= 100_000_000n) {
throw { message: "Bank is not sufficiently provisionned (< 100 Ada)" };
}
- logInfo("Bank is provisionned enough");
+ logDebug("Bank is provisionned enough to run tests");
const participants = await bank.provision(provisionRequest);
+ logDebug("Participants provisionned");
await bank.waitRuntimeSyncingTillCurrentWalletTip(mkRestClient(testConfiguration.runtimeURL));
logInfo("Test Environment : Ready");
diff --git a/packages/testing-kit/src/logging.ts b/packages/testing-kit/src/logging.ts
index 26eaf031..33a83a06 100644
--- a/packages/testing-kit/src/logging.ts
+++ b/packages/testing-kit/src/logging.ts
@@ -2,7 +2,7 @@ import { MarloweJSON } from "@marlowe.io/adapter/codec";
import { WalletTestAPI } from "./wallet/api.js";
export const logDebug = (message: string) =>
- process.env.LOG_DEBUG_LEVEL !== undefined && JSON.parse(process.env.LOG_DEBUG_LEVEL) === true
+ process.env.LOG_DEBUG_LEVEL !== undefined && process.env.LOG_DEBUG_LEVEL === "1"
? console.log(`## ||| [${message}]`)
: {};
diff --git a/packages/testing-kit/src/wallet/lucid/index.ts b/packages/testing-kit/src/wallet/lucid/index.ts
index e40be5ab..053d9d16 100644
--- a/packages/testing-kit/src/wallet/lucid/index.ts
+++ b/packages/testing-kit/src/wallet/lucid/index.ts
@@ -11,7 +11,7 @@ import { Lucid } from "lucid-cardano";
import { WalletAPI, mkLucidWallet } from "@marlowe.io/wallet";
import { RestClient } from "@marlowe.io/runtime-rest-client";
-import { logInfo, logWarning } from "../../logging.js";
+import { logDebug, logInfo, logWarning } from "../../logging.js";
export * as Provision from "./provisionning.js";
import * as Provision from "./provisionning.js";
@@ -55,11 +55,13 @@ const waitRuntimeSyncingTillCurrentWalletTip =
(di: WalletTestDI) =>
async (client: RestClient): Promise => {
const { lucid } = di;
+ logInfo("Waiting for Runtime to sync with the Wallet");
const currentLucidSlot = BigInt(lucid.currentSlot());
- logInfo(`Setting up a synchronization point with Runtime at SlotNo ${currentLucidSlot}`);
+ logInfo(`Setting up a synchronization point with Runtime at slot ${currentLucidSlot}`);
await waitForPredicatePromise(isRuntimeChainMoreAdvancedThan(client, currentLucidSlot));
- process.stdout.write("\n");
- return sleep(15);
+ logInfo(`Runtime and Wallet passsed both ${currentLucidSlot} slot.`);
+ // This sleep will be removed when we have a better tip for runtime...
+ return sleep(20);
};
/**
@@ -70,11 +72,13 @@ const waitRuntimeSyncingTillCurrentWalletTip =
*/
export const isRuntimeChainMoreAdvancedThan = (client: RestClient, aSlotNo: bigint) => () =>
client.healthcheck().then((status) => {
+ logDebug(`Runtime Chain Tip SlotNo : ${status.tips.runtimeChain.blockHeader.slotNo}`);
+ logDebug(`Wallet Chain Tip SlotNo : ${aSlotNo}`);
if (status.tips.runtimeChain.blockHeader.slotNo >= aSlotNo) {
return true;
} else {
const delta = aSlotNo - status.tips.runtimeChain.blockHeader.slotNo;
- process.stdout.write(`Waiting Runtime to reach that point (${delta} slots behind (~${delta}s)) `);
+ logDebug(`Waiting Runtime to reach that point (${delta} slots behind (~${delta}s)) `);
return false;
}
});
diff --git a/packages/testing-kit/src/wallet/lucid/provisionning.ts b/packages/testing-kit/src/wallet/lucid/provisionning.ts
index d043bf69..3fd7ee17 100644
--- a/packages/testing-kit/src/wallet/lucid/provisionning.ts
+++ b/packages/testing-kit/src/wallet/lucid/provisionning.ts
@@ -132,7 +132,7 @@ const mkPolicyWithDeadlineAndOneAuthorizedSigner =
const toAssetsToTransfer = (assets: RuntimeCore.Assets): LucidAssets => {
var lucidAssets: { [key: string]: bigint } = {};
- lucidAssets["lovelace"] = assets.lovelaces;
+ lucidAssets["lovelace"] = assets.lovelaces ?? 0n;
assets.tokens.map(
(token) => (lucidAssets[toUnit(token.assetId.policyId, fromText(token.assetId.assetName))] = token.quantity)
);
diff --git a/packages/wallet/src/lucid/index.ts b/packages/wallet/src/lucid/index.ts
index 90036bb2..06d429cd 100644
--- a/packages/wallet/src/lucid/index.ts
+++ b/packages/wallet/src/lucid/index.ts
@@ -102,7 +102,7 @@ const waitConfirmation =
try {
return await lucid.awaitTx(txHash);
} catch (reason) {
- throw new Error(`Error while awiting : ${reason}`);
+ throw new Error(`Error while awaiting : ${reason}`);
}
};
/**
From 6378fb2c340fbf858739e3b51d71704d2e6f97ec Mon Sep 17 00:00:00 2001
From: Nicolas Henin
Date: Sat, 11 May 2024 13:04:18 +0200
Subject: [PATCH 13/14] account Deposit Integration (runtime V1.0.0)
---
...icolas.henin_runtime_lifecycle_refactor.md | 43 +++++++++++++++++++
.../rest/src/contract/endpoints/collection.ts | 10 ++---
.../core/src/contract/accountDeposits.ts | 6 +--
.../lifecycle/src/generic/contracts.ts | 6 +--
4 files changed, 54 insertions(+), 11 deletions(-)
create mode 100644 changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md
diff --git a/changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md b/changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md
new file mode 100644
index 00000000..1d41d845
--- /dev/null
+++ b/changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md
@@ -0,0 +1,43 @@
+### General
+
+- Feat: **Initial Account Deposits Feature Integration (Runtime v1.0.0):**
+
+ - **Purpose:** This update introduces the capability for users to make initial deposits into their accounts upon creation. This feature aims to streamline the account setup process and enhance user experience.
+ - **Benefits:** This feature squashes the Contract Creation and Initial Input Deposits into 1 transaction instead of multiple ones.
+
+- Feat: **Introduction of a New Contract API in the Runtime Lifecycle API:**
+ - **Purpose:** The addition of a new Contract API is designed to provide developers with more flexibility and control (contract instance concept) over smart contract management within the runtime environment.
+ - **Benefits:** Developers can now leverage enhanced functionalities for deploying, updating, and interacting with smart contracts. This API simplifies complex contract operations and supports more robust smart contract development.
+
+### @marlowe.io/runtime-rest-client
+
+- Feat: `initial account deposits` (runtime v1.0.0) for Contract Creation (`BuildCreateContractTxRequest` via `buildCreateContractTx`)([PR-188](https://github.com/input-output-hk/marlowe-ts-sdk/pull/188))
+
+### @marlowe.io/runtime-core
+
+- Feat: `initial account deposits` (runtime v1.0.0) for Contract Creation ([PR-188](https://github.com/input-output-hk/marlowe-ts-sdk/pull/188)):
+ - Added `export type AccountDeposits = { [key in AddressOrRole]: AssetsMap };` and associated utility functions.
+
+### @marlowe.io/runtime-lifecycle
+
+- Feat: New Contract API `packages/runtime/lifecycle/src/generic/new-contract-api.ts` ([PR-188](https://github.com/input-output-hk/marlowe-ts-sdk/pull/188)):
+ - Generic `waitConfirmation()` : same for contract creation and apply inputs
+ - Seamless Integration of Applicable Actions API
+ - simplfied interface (`create` and `load` with a concept of `ContractInstance` object)
+ - see end to end tests for examples (e.g : `swap.ada.token.e2e.spec.ts`)
+- Feat: `initial account deposits` feature (runtime v1.0.0) for Contract Creation ([PR-188](https://github.com/input-output-hk/marlowe-ts-sdk/pull/188)):
+ - new parameter field `accountDeposits` in
+ - e.g
+
+```ts
+const sellerContractInstance = await sellerLifecycle.newContractAPI.create({
+ contract: swapContract,
+ roles: { [scheme.ask.buyer.role_token]: mintRole("OpenRole") },
+ accountDeposits: mkaccountDeposits([[scheme.offer.seller, seller.assetsProvisioned]]),
+});
+```
+
+### @marlowe.io/language-examples
+
+- Feat: `Atomic swap v2` : Simplified version using the new runtime `v1.0.0` feature (`initial account deposits`)
+ - see end to end tests for examples (e.g : `swap.ada.token.e2e.spec.ts`)
diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts
index 124c5cca..745cac37 100644
--- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts
+++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts
@@ -35,8 +35,8 @@ import {
AddressBech32Guard,
ContractId,
ContractIdGuard,
- accountDeposits,
- accountDepositsGuard,
+ AccountDeposits,
+ AccountDepositsGuard,
} from "@marlowe.io/runtime-core";
import { ContractHeader, ContractHeaderGuard } from "../header.js";
import { RolesConfiguration, RolesConfigurationGuard } from "../rolesConfigurations.js";
@@ -193,7 +193,7 @@ export type BuildCreateContractTxRequestWithContract = {
export const BuildCreateContractTxRequestOptionsGuard = assertGuardEqual(
proxy(),
t.intersection([
- t.type({ changeAddress: AddressBech32Guard, version: MarloweVersion, accounts: accountDepositsGuard }),
+ t.type({ changeAddress: AddressBech32Guard, version: MarloweVersion, accounts: AccountDepositsGuard }),
t.partial({
roles: RolesConfigurationGuard,
threadRoleName: G.RoleName,
@@ -498,7 +498,7 @@ export interface BuildCreateContractTxRequestOptions {
* describe an initial deposit of assets for the accounts in the contract. The key is the address or role name and the value is the assets.
* These assets will be deposited in the accounts at the creation of the contract, and will come from the contract creator's wallet.
*/
- accounts: accountDeposits;
+ accounts: AccountDeposits;
/**
* The Marlowe validator version to use.
@@ -525,7 +525,7 @@ export const PostContractsRequest = t.intersection([
contract: ContractOrSourceIdGuard,
tags: TagsGuard,
metadata: MetadataGuard,
- accounts: accountDepositsGuard,
+ accounts: AccountDepositsGuard,
}),
t.partial({ roles: RolesConfigurationGuard }),
t.partial({ threadTokenName: G.RoleName }),
diff --git a/packages/runtime/core/src/contract/accountDeposits.ts b/packages/runtime/core/src/contract/accountDeposits.ts
index cf8f3c77..f539d6cf 100644
--- a/packages/runtime/core/src/contract/accountDeposits.ts
+++ b/packages/runtime/core/src/contract/accountDeposits.ts
@@ -7,17 +7,17 @@ export const AddressOrRoleGuard = t.string;
/**
* A map of tags to their content. The key is a string, the value can be anything.
*/
-export type accountDeposits = { [key in AddressOrRole]: AssetsMap };
+export type AccountDeposits = { [key in AddressOrRole]: AssetsMap };
/**
* @hidden
*/
-export const accountDepositsGuard = t.record(AddressOrRoleGuard, AssetsMapGuard);
+export const AccountDepositsGuard = t.record(AddressOrRoleGuard, AssetsMapGuard);
/**
* a function that takes a list of party with associated assets and returns an accountDeposits
* the party object is converted to a string using partyToString.
* @param accounts
* @returns
*/
-export function mkaccountDeposits(accounts: [Party, Assets][]): accountDeposits {
+export function mkaccountDeposits(accounts: [Party, Assets][]): AccountDeposits {
return Object.fromEntries(accounts.map(([party, assets]) => [partyToString(party), mapAsset(assets)]));
}
diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts
index 4ac357bf..73969590 100644
--- a/packages/runtime/lifecycle/src/generic/contracts.ts
+++ b/packages/runtime/lifecycle/src/generic/contracts.ts
@@ -13,7 +13,7 @@ import {
StakeAddressBech32,
Metadata,
Tags,
- accountDeposits,
+ AccountDeposits,
} from "@marlowe.io/runtime-core";
import { FPTSRestAPI, RestClient, RestDI, ItemRange, DeprecatedRestDI } from "@marlowe.io/runtime-rest-client";
@@ -93,10 +93,10 @@ export interface CreateContractRequestBase {
* Assets will be withdrawn from the creator's wallet and deposited into the participant's accounts when the contract is created.
*
* @see
- * - {@link @marlowe.io/runtime-core!accountDeposits}
+ * - {@link @marlowe.io/runtime-core!index.AccountDeposits}
*/
- accountDeposits?: accountDeposits;
+ accountDeposits?: AccountDeposits;
/**
* Role Token Configuration for the new contract passed in the `contract` field.
*
From a058f7f6886d2a5aaa8723a0d24eed90790bf692 Mon Sep 17 00:00:00 2001
From: Nicolas Henin
Date: Sat, 11 May 2024 12:20:35 +0200
Subject: [PATCH 14/14] format & updated change log
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
Readme.md | 5 -----
...icolas.henin_runtime_lifecycle_refactor.md | 8 +++----
examples/rest-client-flow/index.html | 8 +------
jsdelivr-npm-importmap.js | 6 ++---
packages/adapter/src/io-ts.ts | 10 ++-------
.../core/src/contract/accountDeposits.ts | 1 +
.../runtime/lifecycle/src/browser/index.ts | 6 +----
packages/runtime/lifecycle/src/index.ts | 22 ++++++-------------
.../runtime/lifecycle/src/nodejs/index.ts | 12 ++--------
9 files changed, 20 insertions(+), 58 deletions(-)
diff --git a/Readme.md b/Readme.md
index f45f3d74..7f222a80 100644
--- a/Readme.md
+++ b/Readme.md
@@ -79,8 +79,3 @@ Prototypes have been also built on top of this sdk:
To report a bug or request a new feature, please look through existing [Github Issues](https://github.com/input-output-hk/marlowe-ts-sdk/issues) before opening a new one.
To help in the development of this SDK, please refer to [this document](./doc/howToDevelop.md).
-
-
-
-
-
diff --git a/changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md b/changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md
index 1d41d845..80c0f2a7 100644
--- a/changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md
+++ b/changelog.d/20240511_113958_nicolas.henin_runtime_lifecycle_refactor.md
@@ -11,7 +11,7 @@
### @marlowe.io/runtime-rest-client
-- Feat: `initial account deposits` (runtime v1.0.0) for Contract Creation (`BuildCreateContractTxRequest` via `buildCreateContractTx`)([PR-188](https://github.com/input-output-hk/marlowe-ts-sdk/pull/188))
+- Feat: `initial account deposits` (runtime v1.0.0) for Contract Creation (`BuildCreateContractTxRequest` via `buildCreateContractTx`):([PR-188](https://github.com/input-output-hk/marlowe-ts-sdk/pull/188))
### @marlowe.io/runtime-core
@@ -24,10 +24,10 @@
- Generic `waitConfirmation()` : same for contract creation and apply inputs
- Seamless Integration of Applicable Actions API
- simplfied interface (`create` and `load` with a concept of `ContractInstance` object)
- - see end to end tests for examples (e.g : `swap.ada.token.e2e.spec.ts`)
+ - see end-to-end tests for examples (e.g : `swap.ada.token.e2e.spec.ts`)
- Feat: `initial account deposits` feature (runtime v1.0.0) for Contract Creation ([PR-188](https://github.com/input-output-hk/marlowe-ts-sdk/pull/188)):
- new parameter field `accountDeposits` in
- - e.g
+ - e.g.
```ts
const sellerContractInstance = await sellerLifecycle.newContractAPI.create({
@@ -40,4 +40,4 @@ const sellerContractInstance = await sellerLifecycle.newContractAPI.create({
### @marlowe.io/language-examples
- Feat: `Atomic swap v2` : Simplified version using the new runtime `v1.0.0` feature (`initial account deposits`)
- - see end to end tests for examples (e.g : `swap.ada.token.e2e.spec.ts`)
+ - see end-to-end tests for examples (e.g : `swap.ada.token.e2e.spec.ts`)
diff --git a/examples/rest-client-flow/index.html b/examples/rest-client-flow/index.html
index ced4ce46..bafe4be2 100644
--- a/examples/rest-client-flow/index.html
+++ b/examples/rest-client-flow/index.html
@@ -25,13 +25,7 @@
Request
This should be filled with a JSON object that starts with an array, where each element is a numbered
parameter.
-
+
diff --git a/jsdelivr-npm-importmap.js b/jsdelivr-npm-importmap.js
index 137f2635..a357d258 100644
--- a/jsdelivr-npm-importmap.js
+++ b/jsdelivr-npm-importmap.js
@@ -42,10 +42,8 @@ const importMap = {
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-specification-client@0.4.0-beta-rc1/dist/bundled/esm/language-specification-client.js",
"@marlowe.io/token-metadata-client":
"https://cdn.jsdelivr.net/npm/@marlowe.io/token-metadata-client@0.4.0-beta-rc1/dist/bundled/esm/token-metadata-client.js",
- "@marlowe.io/wallet":
- "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/wallet.js",
- "@marlowe.io/wallet/api":
- "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/api.js",
+ "@marlowe.io/wallet": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/wallet.js",
+ "@marlowe.io/wallet/api": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/api.js",
"@marlowe.io/wallet/browser":
"https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.4.0-beta-rc1/dist/bundled/esm/browser.js",
"@marlowe.io/wallet/lucid":
diff --git a/packages/adapter/src/io-ts.ts b/packages/adapter/src/io-ts.ts
index 5d1344bc..04399a5b 100644
--- a/packages/adapter/src/io-ts.ts
+++ b/packages/adapter/src/io-ts.ts
@@ -100,11 +100,7 @@ export function formatValidationErrors(errors: Errors): string {
}).join("\n");
}
-export function dynamicAssertType(
- guard: G,
- value: unknown,
- message?: string
-): t.TypeOf {
+export function dynamicAssertType(guard: G, value: unknown, message?: string): t.TypeOf {
const result = guard.decode(value);
if (Either.isLeft(result)) {
throw new InvalidTypeError(guard, value, result.left, message);
@@ -123,9 +119,7 @@ export class InvalidTypeError extends Error {
public readonly errors: Errors,
message?: string
) {
- const msg =
- message ??
- `Unexpected type for value:\n${formatValidationErrors(errors)}`;
+ const msg = message ?? `Unexpected type for value:\n${formatValidationErrors(errors)}`;
super(msg);
}
}
diff --git a/packages/runtime/core/src/contract/accountDeposits.ts b/packages/runtime/core/src/contract/accountDeposits.ts
index f539d6cf..fa5b37bb 100644
--- a/packages/runtime/core/src/contract/accountDeposits.ts
+++ b/packages/runtime/core/src/contract/accountDeposits.ts
@@ -6,6 +6,7 @@ export type AddressOrRole = string;
export const AddressOrRoleGuard = t.string;
/**
* A map of tags to their content. The key is a string, the value can be anything.
+ * New feature from runtime v1.0.0 (initial account deposits))
*/
export type AccountDeposits = { [key in AddressOrRole]: AssetsMap };
/**
diff --git a/packages/runtime/lifecycle/src/browser/index.ts b/packages/runtime/lifecycle/src/browser/index.ts
index 457abc68..5889e5c0 100644
--- a/packages/runtime/lifecycle/src/browser/index.ts
+++ b/packages/runtime/lifecycle/src/browser/index.ts
@@ -80,11 +80,7 @@ export async function mkRuntimeLifecycle(
strict = true
): Promise {
dynamicAssertType(BrowserRuntimeLifecycleOptionsGuard, options);
- dynamicAssertType(
- t.boolean,
- strict,
- "Invalid type for argument 'strict', expected boolean"
- );
+ dynamicAssertType(t.boolean, strict, "Invalid type for argument 'strict', expected boolean");
const { runtimeURL, walletName } = options;
const wallet = await mkBrowserWallet(walletName);
diff --git a/packages/runtime/lifecycle/src/index.ts b/packages/runtime/lifecycle/src/index.ts
index 276741a8..5e765b65 100644
--- a/packages/runtime/lifecycle/src/index.ts
+++ b/packages/runtime/lifecycle/src/index.ts
@@ -42,12 +42,11 @@ export interface RuntimeLifecycleOptions {
/**
* @hidden
*/
-export const RuntimeLifecycleOptionsGuard: t.Type =
- t.type({
- runtimeURL: t.string,
- // TODO: Create a shallow guard for the wallet that checks that all methods are present as t.function.
- wallet: t.any,
- });
+export const RuntimeLifecycleOptionsGuard: t.Type = t.type({
+ runtimeURL: t.string,
+ // TODO: Create a shallow guard for the wallet that checks that all methods are present as t.function.
+ wallet: t.any,
+});
/**
* Creates an instance of RuntimeLifecycle.
@@ -55,16 +54,9 @@ export const RuntimeLifecycleOptionsGuard: t.Type =
* @param strict Whether to perform runtime checking to provide helpful error messages. May have a slight negative performance impact. Default value is `true`.
* @category RuntimeLifecycle
*/
-export function mkRuntimeLifecycle(
- options: RuntimeLifecycleOptions,
- strict = true
-): RuntimeLifecycle {
+export function mkRuntimeLifecycle(options: RuntimeLifecycleOptions, strict = true): RuntimeLifecycle {
dynamicAssertType(RuntimeLifecycleOptionsGuard, options);
- dynamicAssertType(
- t.boolean,
- strict,
- "Invalid type for argument 'strict', expected boolean"
- );
+ dynamicAssertType(t.boolean, strict, "Invalid type for argument 'strict', expected boolean");
const { runtimeURL, wallet } = options;
const deprecatedRestAPI = mkFPTSRestClient(runtimeURL);
const restClient = mkRestClient(runtimeURL, strict);
diff --git a/packages/runtime/lifecycle/src/nodejs/index.ts b/packages/runtime/lifecycle/src/nodejs/index.ts
index 40c4fb23..cbc4a8cb 100644
--- a/packages/runtime/lifecycle/src/nodejs/index.ts
+++ b/packages/runtime/lifecycle/src/nodejs/index.ts
@@ -6,16 +6,8 @@ import { Lucid } from "lucid-cardano";
import * as t from "io-ts/lib/index.js";
import { dynamicAssertType } from "@marlowe.io/adapter/io-ts";
-export async function mkRuntimeLifecycle(
- runtimeURL: string,
- lucid: Lucid,
- strict = true
-): Promise {
- dynamicAssertType(
- t.boolean,
- strict,
- "Invalid type for argument 'strict', expected boolean"
- );
+export async function mkRuntimeLifecycle(runtimeURL: string, lucid: Lucid, strict = true): Promise {
+ dynamicAssertType(t.boolean, strict, "Invalid type for argument 'strict', expected boolean");
const wallet = await Wallet.mkLucidWallet(lucid);
const deprecatedRestAPI = mkFPTSRestClient(runtimeURL);
const restClient = mkRestClient(runtimeURL, strict);