From 0fa4ec359ceecb3b5f9675fae3f624730ca9f5bb Mon Sep 17 00:00:00 2001 From: nhenin Date: Thu, 23 Nov 2023 18:46:20 +0100 Subject: [PATCH 01/13] renamed createContract > buildCreateContractTx (#Discussion) --- examples/rest-client-flow/index.html | 6 +++--- .../rest/src/contract/endpoints/collection.ts | 10 +++++----- .../runtime/client/rest/src/contract/index.ts | 4 ++-- packages/runtime/client/rest/src/index.ts | 17 ++++++++++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/examples/rest-client-flow/index.html b/examples/rest-client-flow/index.html index 9a5d6b69..0ee0f410 100644 --- a/examples/rest-client-flow/index.html +++ b/examples/rest-client-flow/index.html @@ -76,14 +76,14 @@

Request

>
POSTs a new contract /contracts
diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index a77705f2..a81f75db 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -192,10 +192,10 @@ export const GetContractsResponseGuard = assertGuardEqual( ); /** - * Request options for the {@link index.RestClient#createContract | Create contract } endpoint + * Request options for the {@link index.RestClient#buildCreateContractTx | Create contract } endpoint * @category Endpoints */ -export interface CreateContractRequest { +export interface BuildCreateContractTxRequest { // FIXME: create ticket to add stake address // stakeAddress: void; /** @@ -256,7 +256,7 @@ export interface CreateContractRequest { export type POST = ( postContractsRequest: PostContractsRequest, addressesAndCollaterals: AddressesAndCollaterals -) => TE.TaskEither; +) => TE.TaskEither; /** * @hidden @@ -276,7 +276,7 @@ export const PostContractsRequest = t.intersection([ t.partial({ roles: RolesConfig }), ]); -export interface CreateContractResponse { +export interface BuildCreateContractTxResponse { /** * This is the ID the contract will have after it is signed and submitted. */ @@ -300,7 +300,7 @@ export interface CreateContractResponse { * @hidden */ const CreateContractResponseGuard = assertGuardEqual( - proxy(), + proxy(), t.type({ contractId: ContractIdGuard, safetyErrors: t.UnknownArray, diff --git a/packages/runtime/client/rest/src/contract/index.ts b/packages/runtime/client/rest/src/contract/index.ts index 632819e8..630b5db7 100644 --- a/packages/runtime/client/rest/src/contract/index.ts +++ b/packages/runtime/client/rest/src/contract/index.ts @@ -12,8 +12,8 @@ export { GetContractsResponse, GetContractsRequest, ContractsRange, - CreateContractRequest, - CreateContractResponse, + BuildCreateContractTxRequest, + BuildCreateContractTxResponse, } from "./endpoints/collection.js"; export { TxHeader } from "./transaction/header.js"; export { diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index d8eb263a..ad47bcee 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -89,12 +89,9 @@ export interface RestClient { * @throws DecodingError - If the response from the server can't be decoded * @see {@link https://docs.marlowe.iohk.io/api/create-a-new-contract | The backend documentation} */ - // TODO: Jamie, remove the `s from the end of the endpoint name in the docs site - // DISCUSSION: @Jamie, @N.H: Should this be called `buildCreateContractTx` instead? As it is not creating the - // contract, rather it is creating the transaction to be signed - createContract( - request: Contracts.CreateContractRequest - ): Promise; + buildCreateContractTx( + request: Contracts.BuildCreateContractTxRequest + ): Promise; /** * Uploads a marlowe-object bundle to the runtime, giving back the hash of the main contract and the hashes of the intermediate objects. @@ -135,6 +132,9 @@ export interface RestClient { * Create an unsigned transaction which applies inputs to a contract. * @see {@link https://docs.marlowe.iohk.io/api/apply-inputs-to-contract | The backend documentation} */ + // TODO: Jamie, remove the `s from the end of the endpoint name in the docs site + // DISCUSSION: @Jamie, @N.H: Should this be called `buildApplyInputsToContractTx` instead? As it is not applying inputs to the + // contract, rather it is creating the transaction to be signed applyInputsToContract( request: Transactions.ApplyInputsToContractRequest ): Promise; @@ -166,6 +166,9 @@ export interface RestClient { * Build an unsigned transaction (sign with the {@link @marlowe.io/wallet!api.WalletAPI#signTx} procedure) which withdraws available payouts from a contract (when applied with the {@link @marlowe.io/runtime-rest-client!index.RestClient#submitWithdrawal} procedure). * @see {@link https://docs.marlowe.iohk.io/api/withdraw-payouts | The backend documentation} */ + // TODO: Jamie, remove the `s from the end of the endpoint name in the docs site + // DISCUSSION: @Jamie, @N.H: Should this be called `buildWithdrawPayoutsTx` instead? As it is not withdrawing the + // payout, rather it is creating the transaction to be signed withdrawPayouts( request: Withdrawals.WithdrawPayoutsRequest ): Promise; @@ -257,7 +260,7 @@ export function mkRestClient(baseURL: string): RestClient { getContractById(contractId) { return unsafeTaskEither(Contract.getViaAxios(axiosInstance)(contractId)); }, - createContract(request) { + buildCreateContractTx(request) { const postContractsRequest = { contract: request.contract, version: request.version, From a43a4c4f9e1dad08cf207f0c524de5239a179e4f Mon Sep 17 00:00:00 2001 From: nhenin Date: Sat, 25 Nov 2023 11:35:55 +0100 Subject: [PATCH 02/13] Stake Address At Contract Creation Support --- .../rest/src/contract/endpoints/collection.ts | 18 ++++++++++++++---- packages/runtime/client/rest/src/index.ts | 3 ++- packages/runtime/core/src/address.ts | 9 ++++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index a81f75db..c6c9953e 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -32,6 +32,8 @@ import { TxOutRef, AssetId, unPolicyId, + StakeAddressBech32, + unStakeAddressBech32, } from "@marlowe.io/runtime-core"; import { ContractHeader, ContractHeaderGuard } from "../header.js"; @@ -196,8 +198,11 @@ export const GetContractsResponseGuard = assertGuardEqual( * @category Endpoints */ export interface BuildCreateContractTxRequest { - // FIXME: create ticket to add stake address - // stakeAddress: void; + /** + * The Marlowe Runtime utilizes this Optional field to set a stake address + * where to send staking rewards for the Marlowe script outputs of this contract. + */ + stakeAddress?: StakeAddressBech32; /** * The Marlowe Runtime utilizes this mandatory field and any additional addresses provided in `usedAddresses` * to search for UTxOs that can be used to balance the contract creation transaction. @@ -255,7 +260,8 @@ export interface BuildCreateContractTxRequest { export type POST = ( postContractsRequest: PostContractsRequest, - addressesAndCollaterals: AddressesAndCollaterals + addressesAndCollaterals: AddressesAndCollaterals, + stakeAddress?: StakeAddressBech32 ) => TE.TaskEither; /** @@ -317,12 +323,16 @@ export const PostResponse = t.type({ * @see {@link https://docs.marlowe.iohk.io/api/create-contracts} */ export const postViaAxios: (axiosInstance: AxiosInstance) => POST = - (axiosInstance) => (postContractsRequest, addressesAndCollaterals) => + (axiosInstance) => + (postContractsRequest, addressesAndCollaterals, stakeAddress) => pipe( HTTP.Post(axiosInstance)("/contracts", postContractsRequest, { headers: { Accept: "application/vendor.iog.marlowe-runtime.contract-tx-json", "Content-Type": "application/json", + ...(stakeAddress && { + "X-Stake-Address": unStakeAddressBech32(stakeAddress), + }), "X-Change-Address": unAddressBech32( addressesAndCollaterals.changeAddress ), diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index ad47bcee..99074b0a 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -277,7 +277,8 @@ export function mkRestClient(baseURL: string): RestClient { return unsafeTaskEither( Contracts.postViaAxios(axiosInstance)( postContractsRequest, - addressesAndCollaterals + addressesAndCollaterals, + request.stakeAddress ) ); }, diff --git a/packages/runtime/core/src/address.ts b/packages/runtime/core/src/address.ts index 40b52371..8ea5e091 100644 --- a/packages/runtime/core/src/address.ts +++ b/packages/runtime/core/src/address.ts @@ -1,7 +1,6 @@ import * as t from "io-ts/lib/index.js"; import { iso, Newtype } from "newtype-ts"; import { fromNewtype } from "io-ts-types"; -import { optionFromNullable } from "io-ts-types"; import { TxOutRef } from "./tx/outRef.js"; export type AddressBech32 = Newtype< @@ -18,3 +17,11 @@ export const AddressesAndCollaterals = t.type({ usedAddresses: t.array(AddressBech32), collateralUTxOs: t.array(TxOutRef), }); + +export type StakeAddressBech32 = Newtype< + { readonly StakeAddressBech32: unique symbol }, + string +>; +export const StakeAddressBech32 = fromNewtype(t.string); +export const unStakeAddressBech32 = iso().unwrap; +export const stakeAddressBech32 = iso().wrap; From 11bb9ac9423447ac40fff534552eb2fe55359b2d Mon Sep 17 00:00:00 2001 From: nhenin Date: Sat, 25 Nov 2023 13:30:44 +0100 Subject: [PATCH 03/13] minUTxODeposit documented and changed as an Optional field --- .../rest/src/contract/endpoints/collection.ts | 15 +++++++++------ packages/runtime/client/rest/src/index.ts | 4 ++-- packages/runtime/lifecycle/src/api.ts | 4 +--- .../runtime/lifecycle/src/generic/contracts.ts | 11 +++-------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index c6c9953e..2a1b4624 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -237,13 +237,16 @@ export interface BuildCreateContractTxRequest { // TODO: Add link to example of metadata metadata?: Metadata; /** - * To avoid spamming the network, the cardano ledger requires us to deposit a minimum amount of ADA. + * Creating a Marlowe Contracts 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 (mininmumLovelaceUTxODeposit)" that adjust + * the value (in ADA) of each UTxO has been added. The more the UTxOs entries are big in size, the more the value of minimum + * of ADAs needs to be contained. + * (see : https://docs.cardano.org/native-tokens/minimum-ada-value-requirement/) + * This value is computed automatically within the Runtime, so this parameter is only necessary if you need some custom adjustment. * The value is in lovelace, so if you want to deposit 3Ada you need to pass 3_000_000 here. */ - // TODO: @sam - // Create a global documentation page (and link from here) that explains the concept of minUTxO, - // why it is required, who deposits it, and how and when you get it back. - minUTxODeposit: number; + mininmumLovelaceUTxODeposit?: number; // TODO: Comment this and improve the generated type (currently `string | {}`) roles?: RolesConfig; @@ -277,9 +280,9 @@ export const PostContractsRequest = t.intersection([ version: MarloweVersion, tags: TagsGuard, metadata: Metadata, - minUTxODeposit: t.number, }), t.partial({ roles: RolesConfig }), + t.partial({ minUTxODeposit: t.number }), ]); export interface BuildCreateContractTxResponse { diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index 99074b0a..8bca94a3 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -266,8 +266,8 @@ export function mkRestClient(baseURL: string): RestClient { version: request.version, metadata: request.metadata ?? {}, tags: request.tags ?? {}, - minUTxODeposit: request.minUTxODeposit, - roles: request.roles, + ...request.mininmumLovelaceUTxODeposit && {minUTxODeposit: request.mininmumLovelaceUTxODeposit}, + ...request.roles && {roles: request.roles}, }; const addressesAndCollaterals = { changeAddress: request.changeAddress, diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts index 7495627e..70a66078 100644 --- a/packages/runtime/lifecycle/src/api.ts +++ b/packages/runtime/lifecycle/src/api.ts @@ -32,11 +32,9 @@ export type CreateContractRequest = { roles?: RolesConfig; tags?: Tags; metadata?: Metadata; - minUTxODeposit?: number; + mininmumLovelaceUTxODeposit?: number; }; -export const minUTxODepositDefault: number = 3_000_000; - export type ApplyInputsRequest = { inputs: Input[]; tags?: Tags; diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts index bf37ade7..b1425b24 100644 --- a/packages/runtime/lifecycle/src/generic/contracts.ts +++ b/packages/runtime/lifecycle/src/generic/contracts.ts @@ -6,8 +6,7 @@ import { ApplyInputsRequest, ContractsAPI, ContractsDI, - CreateContractRequest, - minUTxODepositDefault, + CreateContractRequest } from "../api.js"; import { getAddressesAndCollaterals, WalletAPI } from "@marlowe.io/wallet/api"; @@ -144,12 +143,8 @@ export const submitCreateTxFpTs: ( version: "v1", roles: createContractRequest.roles, tags: createContractRequest.tags ? createContractRequest.tags : {}, - metadata: createContractRequest.metadata - ? createContractRequest.metadata - : {}, - minUTxODeposit: createContractRequest.minUTxODeposit - ? createContractRequest.minUTxODeposit - : minUTxODepositDefault, + metadata: createContractRequest.metadata ? createContractRequest.metadata: {}, + ...createContractRequest.mininmumLovelaceUTxODeposit && {mininmumLovelaceUTxODeposit : createContractRequest.mininmumLovelaceUTxODeposit}, }, addressesAndCollaterals ) From ab4575329f45fc1c706e642691da0cff79986f9e Mon Sep 17 00:00:00 2001 From: nhenin Date: Sat, 25 Nov 2023 18:32:16 +0100 Subject: [PATCH 04/13] Documentation improved for Build Create Contract Tx Endpoint --- .../client/rest/src/contract/details.ts | 4 +- .../rest/src/contract/endpoints/collection.ts | 81 +++++++++++++++---- .../client/rest/src/contract/header.ts | 4 +- .../runtime/client/rest/src/contract/role.ts | 4 + packages/runtime/client/rest/src/index.ts | 6 +- .../lifecycle/src/generic/contracts.ts | 11 ++- 6 files changed, 85 insertions(+), 25 deletions(-) diff --git a/packages/runtime/client/rest/src/contract/details.ts b/packages/runtime/client/rest/src/contract/details.ts index 2a92e088..823a0c0c 100644 --- a/packages/runtime/client/rest/src/contract/details.ts +++ b/packages/runtime/client/rest/src/contract/details.ts @@ -40,15 +40,17 @@ export const Payout = t.type({ }); /** - * Represents the response of the {@link index.RestClient#getContractById | Get contract by id } endpoint + * Represents the response of the {@link index.RestClient#getContractById | Get Contract By Id } endpoint * @see The {@link ContractDetails:var | dynamic validator} for this type. * @interface + * @category Endpoint : Get Contract By Id */ export interface ContractDetails extends t.TypeOf {} /** * This is a {@link !io-ts-usage | Dynamic type validator} for the {@link ContractDetails:type}. * @category Validator + * @category Endpoint : Get Contract By Id */ // DISCUSSION : Tags are missing in the ts-sdk and available in the REST API export const ContractDetails = t.type({ diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index 2a1b4624..4e91e088 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -42,13 +42,13 @@ import { RolesConfig } from "../role.js"; import { ContractId, ContractIdGuard } from "@marlowe.io/runtime-core"; /** - * @category GetContractsResponse + * @category Endpoint : Get Contracts */ export interface ContractsRange extends Newtype<{ readonly ContractsRange: unique symbol }, string> {} /** - * @category GetContractsResponse + * @category Endpoint : Get Contracts */ export const ContractsRange = fromNewtype(t.string); export const unContractsRange = iso().unwrap; @@ -56,7 +56,7 @@ export const contractsRange = iso().wrap; /** * Request options for the {@link index.RestClient#getContracts | Get contracts } endpoint - * @category Endpoints + * @category Endpoint : Get Contracts */ export interface GetContractsRequest { /** @@ -158,7 +158,7 @@ export const GETByRangeRawResponse = t.type({ /** * Represents the response of the {@link index.RestClient#getContracts | Get contracts } endpoint - * @category GetContractsResponse + * @category Endpoint : Get Contracts */ export interface GetContractsResponse { /** @@ -194,8 +194,8 @@ export const GetContractsResponseGuard = assertGuardEqual( ); /** - * Request options for the {@link index.RestClient#buildCreateContractTx | Create contract } endpoint - * @category Endpoints + * Request options for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint + * @category Endpoint : Build Create Contract Tx */ export interface BuildCreateContractTxRequest { /** @@ -224,7 +224,18 @@ export interface BuildCreateContractTxRequest { */ usedAddresses?: AddressBech32[]; /** - * TODO: Document + * UTxOs provided as collateral in case the Tx built will unexpectedly fail at the submit phase. + * @justification + * The collateral mechanism is an important feature that has been designed to ensure + * successful smart contract execution. + * Collateral is used to guarantee that nodes are compensated for their work in case phase-2 validation fails. + * Thus, collateral is the monetary guarantee a user gives to assure that the contract has been carefully designed + * and thoroughly tested. Collateral amount is specified at the time of constructing the transaction. + * Not directly, but by adding collateral inputs to the transaction. The total balance in the UTXOs + * corresponding to these specially marked inputs is the transaction’s collateral amount. + * If the user fulfills the conditions of the guarantee, and a contract gets executed, the collateral is safe. + * @see + * https://docs.cardano.org/smart-contracts/plutus/collateral-mechanism */ collateralUTxOs?: TxOutRef[]; /** @@ -232,31 +243,63 @@ export interface BuildCreateContractTxRequest { */ contract: Contract; /** - * An object containing metadata about the contract + * Marlowe Tags are stored as Metadata within the Transaction Metadata under the top-level Marlowe Reserved Key (`1564`). + * Tags allows to Query created Marlowe Contracts via {@link index.RestClient#getContracts | Get contracts } + * @remarks + * 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. + * @remarks + * Metadata can be expressed as a JSON object with some restrictions: + * - All top-level keys must be integers between 0 and 2^64 - 1. + * - Each metadata value is tagged with its type. + * - Strings must be at most 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^64 - 1)` to `2^64 - 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 */ - // TODO: Add link to example of 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 Contracts 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 (mininmumLovelaceUTxODeposit)" that adjust * the value (in ADA) of each UTxO has been added. The more the UTxOs entries are big in size, the more the value of minimum * of ADAs needs to be contained. - * (see : https://docs.cardano.org/native-tokens/minimum-ada-value-requirement/) - * This value is computed automatically within the Runtime, so this parameter is only necessary if you need some custom adjustment. - * The value is in lovelace, so if you want to deposit 3Ada you need to pass 3_000_000 here. + * @see + * https://docs.cardano.org/native-tokens/minimum-ada-value-requirement */ mininmumLovelaceUTxODeposit?: number; // TODO: Comment this and improve the generated type (currently `string | {}`) roles?: RolesConfig; - /** - * An optional object of tags where the **key** is the tag name (`string`) and the **value** is the tag content (`any`) - */ - tags?: Tags; /** - * The validator version to use. + * The Marlowe validator version to use. */ version: MarloweVersion; } @@ -285,6 +328,10 @@ export const PostContractsRequest = t.intersection([ t.partial({ minUTxODeposit: t.number }), ]); +/** + * Response for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint + * @category Endpoint : Build Create Contract Tx + */ export interface BuildCreateContractTxResponse { /** * This is the ID the contract will have after it is signed and submitted. diff --git a/packages/runtime/client/rest/src/contract/header.ts b/packages/runtime/client/rest/src/contract/header.ts index 2d1647b1..a316f7fc 100644 --- a/packages/runtime/client/rest/src/contract/header.ts +++ b/packages/runtime/client/rest/src/contract/header.ts @@ -22,7 +22,7 @@ import { TxStatus } from "./transaction/status.js"; * Use {@link index.RestClient#getContractById} to get full contract details * * @see The {@link https://github.com/input-output-hk/marlowe-cardano/blob/b39fe3c3ed67d41cdea6d45700093e7ffa4fad62/marlowe-runtime-web/src/Language/Marlowe/Runtime/Web/Types.hs#L502 | The backend definition } of this type - * @category GetContractsResponse + * @category Endpoint : Get Contracts */ export interface ContractHeader { /** @@ -58,7 +58,7 @@ export interface ContractHeader { /** * This is a {@link !io-ts-usage | Dynamic type validator} for a {@link ContractHeaderGuard:type}. * @category Validator - * @category GetContractsResponse + * @category Endpoint : Get Contracts * @hidden */ export const ContractHeaderGuard = t.type({ diff --git a/packages/runtime/client/rest/src/contract/role.ts b/packages/runtime/client/rest/src/contract/role.ts index bbaef7f5..2b5f1d99 100644 --- a/packages/runtime/client/rest/src/contract/role.ts +++ b/packages/runtime/client/rest/src/contract/role.ts @@ -40,5 +40,9 @@ export const RoleTokenConfig = t.union([RoleTokenSimple, RoleTokenAdvanced]); export type Mint = t.TypeOf; export const Mint = t.record(RoleName, RoleTokenConfig); +/** + * @category Endpoint : Build Create Contract Tx + */ export type RolesConfig = t.TypeOf; + export const RolesConfig = t.union([UsePolicy, Mint]); diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index 8bca94a3..df4c69db 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -266,8 +266,10 @@ export function mkRestClient(baseURL: string): RestClient { version: request.version, metadata: request.metadata ?? {}, tags: request.tags ?? {}, - ...request.mininmumLovelaceUTxODeposit && {minUTxODeposit: request.mininmumLovelaceUTxODeposit}, - ...request.roles && {roles: request.roles}, + ...(request.mininmumLovelaceUTxODeposit && { + minUTxODeposit: request.mininmumLovelaceUTxODeposit, + }), + ...(request.roles && { roles: request.roles }), }; const addressesAndCollaterals = { changeAddress: request.changeAddress, diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts index b1425b24..43c8244d 100644 --- a/packages/runtime/lifecycle/src/generic/contracts.ts +++ b/packages/runtime/lifecycle/src/generic/contracts.ts @@ -6,7 +6,7 @@ import { ApplyInputsRequest, ContractsAPI, ContractsDI, - CreateContractRequest + CreateContractRequest, } from "../api.js"; import { getAddressesAndCollaterals, WalletAPI } from "@marlowe.io/wallet/api"; @@ -143,8 +143,13 @@ export const submitCreateTxFpTs: ( version: "v1", roles: createContractRequest.roles, tags: createContractRequest.tags ? createContractRequest.tags : {}, - metadata: createContractRequest.metadata ? createContractRequest.metadata: {}, - ...createContractRequest.mininmumLovelaceUTxODeposit && {mininmumLovelaceUTxODeposit : createContractRequest.mininmumLovelaceUTxODeposit}, + metadata: createContractRequest.metadata + ? createContractRequest.metadata + : {}, + ...(createContractRequest.mininmumLovelaceUTxODeposit && { + mininmumLovelaceUTxODeposit: + createContractRequest.mininmumLovelaceUTxODeposit, + }), }, addressesAndCollaterals ) From cc0ff2575620afa21d916e0a060a883d8d1e1e4d Mon Sep 17 00:00:00 2001 From: nhenin Date: Mon, 27 Nov 2023 12:47:19 +0100 Subject: [PATCH 05/13] Create Merkleized Contract over Cardano Support --- .../rest/src/contract/endpoints/collection.ts | 25 ++++++++++++++++--- .../runtime/client/rest/src/contract/index.ts | 7 ++++++ packages/runtime/client/rest/src/index.ts | 2 +- packages/runtime/core/src/index.ts | 1 + packages/runtime/core/src/sourceId.ts | 4 +++ 5 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 packages/runtime/core/src/sourceId.ts diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index 4e91e088..59a2d967 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -34,6 +34,8 @@ import { unPolicyId, StakeAddressBech32, unStakeAddressBech32, + SourceId, + SourceIdGuard, } from "@marlowe.io/runtime-core"; import { ContractHeader, ContractHeaderGuard } from "../header.js"; @@ -193,6 +195,21 @@ export const GetContractsResponseGuard = assertGuardEqual( }) ); +/** + * Either a Non Merkleized Marlowe Contract or a Merkleized One + * @category Endpoint : Build Create Contract Tx + */ +export type ContractOrSourceId = Contract | SourceId; + +/** + * Guard for ContractOrSourceId type + * @category Endpoint : Build Create Contract Tx + */ +export const ContractOrSourceIdGuard: t.Type = t.union([ + G.Contract, + SourceIdGuard, +]); + /** * Request options for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint * @category Endpoint : Build Create Contract Tx @@ -238,10 +255,12 @@ export interface BuildCreateContractTxRequest { * https://docs.cardano.org/smart-contracts/plutus/collateral-mechanism */ collateralUTxOs?: TxOutRef[]; + /** - * The contract to create + * A Marlowe Contract or a Merkleized One (referred by its source Id) to create over Cardano + * @see Large/Deep Contracts Support (Contract Merkleization) and `@marlowe.io/language-core` */ - contract: Contract; + contractOrSourceId: ContractOrSourceId; /** * Marlowe Tags are stored as Metadata within the Transaction Metadata under the top-level Marlowe Reserved Key (`1564`). * Tags allows to Query created Marlowe Contracts via {@link index.RestClient#getContracts | Get contracts } @@ -319,7 +338,7 @@ export type PostContractsRequest = t.TypeOf; */ export const PostContractsRequest = t.intersection([ t.type({ - contract: G.Contract, + contract: ContractOrSourceIdGuard, version: MarloweVersion, tags: TagsGuard, metadata: Metadata, diff --git a/packages/runtime/client/rest/src/contract/index.ts b/packages/runtime/client/rest/src/contract/index.ts index 630b5db7..914c945d 100644 --- a/packages/runtime/client/rest/src/contract/index.ts +++ b/packages/runtime/client/rest/src/contract/index.ts @@ -2,6 +2,12 @@ * ```ts * import * as C from "@marlowe.io/runtime-rest-client/contract"; *``` + * This package contains all the implementation and details related to + * endpoints under the URI `/contracts/...` : + * - {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } + * - {@link index.RestClient#getContracts | Get contracts } + * - {@link index.RestClient#getContractById | Get Contract By Id } + * @packageDocumentation */ @@ -12,6 +18,7 @@ export { GetContractsResponse, GetContractsRequest, ContractsRange, + ContractOrSourceId, BuildCreateContractTxRequest, BuildCreateContractTxResponse, } from "./endpoints/collection.js"; diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index df4c69db..ed84827a 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -262,7 +262,7 @@ export function mkRestClient(baseURL: string): RestClient { }, buildCreateContractTx(request) { const postContractsRequest = { - contract: request.contract, + contract: request.contractOrSourceId, version: request.version, metadata: request.metadata ?? {}, tags: request.tags ?? {}, diff --git a/packages/runtime/core/src/index.ts b/packages/runtime/core/src/index.ts index 5f3fa11a..09dc4331 100644 --- a/packages/runtime/core/src/index.ts +++ b/packages/runtime/core/src/index.ts @@ -6,5 +6,6 @@ export * from "./tx/index.js"; export * from "./metadata.js"; export * from "./tag.js"; export * from "./contract/id.js"; +export * from "./sourceId.js"; export * from "./asset/index.js"; export * from "./payout/index.js"; diff --git a/packages/runtime/core/src/sourceId.ts b/packages/runtime/core/src/sourceId.ts new file mode 100644 index 00000000..d4e227ba --- /dev/null +++ b/packages/runtime/core/src/sourceId.ts @@ -0,0 +1,4 @@ +import * as t from "io-ts/lib/index.js"; + +export type SourceId = string; +export const SourceIdGuard = t.string; From 167456e3b5d53749d1e7eff4b7a52b56eb213a9c Mon Sep 17 00:00:00 2001 From: nhenin Date: Tue, 28 Nov 2023 19:28:54 +0100 Subject: [PATCH 06/13] Open Role Support --- packages/adapter/src/codec.ts | 20 ++ packages/language/core/v1/src/address.ts | 3 +- packages/language/core/v1/src/guards.ts | 3 + packages/language/core/v1/src/index.ts | 2 +- packages/language/core/v1/src/participants.ts | 4 +- packages/language/core/v1/src/policyId.ts | 2 +- packages/language/core/v1/src/token.ts | 4 +- packages/runtime/client/rest/package.json | 11 +- .../client/rest/src/contract/details.ts | 4 +- .../rest/src/contract/endpoints/collection.ts | 156 +++++++++-- .../client/rest/src/contract/guards.ts | 27 ++ .../runtime/client/rest/src/contract/index.ts | 24 +- .../runtime/client/rest/src/contract/role.ts | 48 ---- .../rest/src/contract/rolesConfigurations.ts | 250 ++++++++++++++++++ packages/runtime/client/rest/src/index.ts | 18 +- packages/runtime/lifecycle/src/api.ts | 5 +- .../lifecycle/src/generic/contracts.ts | 7 +- .../runtime/lifecycle/src/generic/payouts.ts | 6 +- 18 files changed, 495 insertions(+), 99 deletions(-) create mode 100644 packages/runtime/client/rest/src/contract/guards.ts delete mode 100644 packages/runtime/client/rest/src/contract/role.ts create mode 100644 packages/runtime/client/rest/src/contract/rolesConfigurations.ts diff --git a/packages/adapter/src/codec.ts b/packages/adapter/src/codec.ts index b1a37dfb..0ae7ec29 100644 --- a/packages/adapter/src/codec.ts +++ b/packages/adapter/src/codec.ts @@ -5,6 +5,26 @@ import JSONbigint from "json-bigint"; export type DecodingError = string[]; +/** + * ```ts + * try { + * myRuntimeEndpoint(myRequest); + * } catch (error){ + * if(error instanceOf UnexpectedRuntimeResponse) { + * console.error("The Runtime answered an unexpected message, please contact our desk support",error) + * } else { + * console.error("another type of error",error) + * } + * } + * ``` + */ +export class UnexpectedRuntimeResponse extends Error { + public type = "UnexpectedRuntimeResponse" as const; + constructor(message: string) { + super(); + } +} + export const MarloweJSON = JSONbigint({ alwaysParseAsBig: true, useNativeBigInt: true, diff --git a/packages/language/core/v1/src/address.ts b/packages/language/core/v1/src/address.ts index c0bad8a2..c720451e 100644 --- a/packages/language/core/v1/src/address.ts +++ b/packages/language/core/v1/src/address.ts @@ -1,6 +1,5 @@ import * as t from "io-ts/lib/index.js"; -// TODO: This should be DELETED as there is a newtype for this in runtime-core, -// but a preliminary change broke the build with weird type errors +// TODO: This should be replaced by a Branded Version available in the Runtime Rest Client Package, export type AddressBech32 = string; export const AddressBech32 = t.string; diff --git a/packages/language/core/v1/src/guards.ts b/packages/language/core/v1/src/guards.ts index 5c776d66..4fab54df 100644 --- a/packages/language/core/v1/src/guards.ts +++ b/packages/language/core/v1/src/guards.ts @@ -13,6 +13,8 @@ @packageDocumentation */ +export { PolicyIdGuard as PolicyId } from "./policyId.js"; + export { ActionGuard as Action, DepositGuard as Deposit, @@ -58,6 +60,7 @@ export { export { RoleGuard as Role, + RoleNameGuard as RoleName, PartyGuard as Party, AddressGuard as Address, } from "./participants.js"; diff --git a/packages/language/core/v1/src/index.ts b/packages/language/core/v1/src/index.ts index 87e28b47..0bce3c99 100644 --- a/packages/language/core/v1/src/index.ts +++ b/packages/language/core/v1/src/index.ts @@ -55,7 +55,7 @@ export { MerkleizedInput, } from "./inputs.js"; -export { role, Party, Address, Role } from "./participants.js"; +export { role, Party, Address, Role, RoleName } from "./participants.js"; export { Payee, PayeeAccount, PayeeParty, AccountId } from "./payee.js"; diff --git a/packages/language/core/v1/src/participants.ts b/packages/language/core/v1/src/participants.ts index dfe6ce72..02a2381c 100644 --- a/packages/language/core/v1/src/participants.ts +++ b/packages/language/core/v1/src/participants.ts @@ -29,8 +29,8 @@ export interface Address { */ export const AddressGuard: t.Type
= t.type({ address: AddressBech32 }); -type RoleName = string; -const RoleNameGuard: t.Type = t.string; +export type RoleName = string; +export const RoleNameGuard: t.Type = t.string; /** * Search [[lower-name-builders]] diff --git a/packages/language/core/v1/src/policyId.ts b/packages/language/core/v1/src/policyId.ts index 0412d5f7..b2082f30 100644 --- a/packages/language/core/v1/src/policyId.ts +++ b/packages/language/core/v1/src/policyId.ts @@ -8,4 +8,4 @@ export type PolicyId = string; /** * @category Token */ -export const PolicyId = t.string; +export const PolicyIdGuard = t.string; diff --git a/packages/language/core/v1/src/token.ts b/packages/language/core/v1/src/token.ts index b9e64bff..c330243b 100644 --- a/packages/language/core/v1/src/token.ts +++ b/packages/language/core/v1/src/token.ts @@ -1,6 +1,6 @@ import { Sort, strCmp } from "@marlowe.io/adapter/assoc-map"; import * as t from "io-ts/lib/index.js"; -import { PolicyId } from "./policyId.js"; +import { PolicyId, PolicyIdGuard } from "./policyId.js"; /** * @see {@link @marlowe.io/language-core-v1!index.Token}. * @category Token @@ -26,7 +26,7 @@ export interface Token { * @category Token */ export const TokenGuard: t.Type = t.type({ - currency_symbol: PolicyId, + currency_symbol: PolicyIdGuard, token_name: TokenNameGuard, }); diff --git a/packages/runtime/client/rest/package.json b/packages/runtime/client/rest/package.json index 904d5c83..0f32ef84 100644 --- a/packages/runtime/client/rest/package.json +++ b/packages/runtime/client/rest/package.json @@ -36,12 +36,21 @@ "require": "./dist/bundled/cjs/transaction.cjs", "types": "./dist/esm/contract/transaction/index.d.ts" }, + "./payout": { + "import": "./dist/esm/payout/index.js", + "require": "./dist/bundled/cjs/payout.cjs", + "types": "./dist/esm/payout/index.d.ts" + }, "./withdrawal": { "import": "./dist/esm/withdrawal/index.js", "require": "./dist/bundled/cjs/withdrawal.cjs", "types": "./dist/esm/withdrawal/index.d.ts" }, - "./contract/*": "./dist/esm/contract/*.js" + "./contract": { + "import": "./dist/esm/contract/index.js", + "require": "./dist/bundled/cjs/contract.cjs", + "types": "./dist/esm/contract/index.d.ts" + } }, "dependencies": { "@marlowe.io/adapter": "0.3.0-beta-rc1", diff --git a/packages/runtime/client/rest/src/contract/details.ts b/packages/runtime/client/rest/src/contract/details.ts index 823a0c0c..69993f96 100644 --- a/packages/runtime/client/rest/src/contract/details.ts +++ b/packages/runtime/client/rest/src/contract/details.ts @@ -4,10 +4,8 @@ import { Contract, MarloweState } from "@marlowe.io/language-core-v1"; import * as G from "@marlowe.io/language-core-v1/guards"; import { MarloweVersion } from "@marlowe.io/language-core-v1/version"; import { ContractIdGuard } from "@marlowe.io/runtime-core"; - import { TxStatus } from "./transaction/status.js"; -import { RoleName } from "./role.js"; import { TxOutRef, BlockHeaderGuard, @@ -36,7 +34,7 @@ export const Payout = t.type({ /** * The {@link RoleName} of the participant that has the unclaimed Payout. */ - role: RoleName, + role: G.RoleName, }); /** diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index 59a2d967..69eef071 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -11,7 +11,7 @@ import { formatValidationErrors } from "jsonbigint-io-ts-reporters"; import { fromNewtype, optionFromNullable } from "io-ts-types"; import { stringify } from "qs"; import { assertGuardEqual, proxy } from "@marlowe.io/adapter/io-ts"; -import { Contract } from "@marlowe.io/language-core-v1"; +import { Contract, RoleName } from "@marlowe.io/language-core-v1"; import * as G from "@marlowe.io/language-core-v1/guards"; import { MarloweVersion } from "@marlowe.io/language-core-v1/version"; @@ -39,7 +39,10 @@ import { } from "@marlowe.io/runtime-core"; import { ContractHeader, ContractHeaderGuard } from "../header.js"; -import { RolesConfig } from "../role.js"; +import { + RolesConfiguration, + RolesConfigurationGuard, +} from "../rolesConfigurations.js"; import { ContractId, ContractIdGuard } from "@marlowe.io/runtime-core"; @@ -233,24 +236,36 @@ export interface BuildCreateContractTxRequest { /** * The Marlowe Runtime utilizes the mandatory `changeAddress` and any additional addresses provided here * to search for UTxOs that can be used to balance the contract creation transaction. + * * @remarks + * * 1. When using single address wallets like Nami, it is not necesary to fill this field. * 2. If an address was provided in the `changeAddress` field, it is redundant to include it here (but it doesn't fail). + * * @see WalletAPI function {@link @marlowe.io/wallet!api.WalletAPI#getChangeAddress} * @see WalletAPI function {@link @marlowe.io/wallet!api.WalletAPI#getUsedAddresses} */ usedAddresses?: AddressBech32[]; /** * UTxOs provided as collateral in case the Tx built will unexpectedly fail at the submit phase. - * @justification + * + *

Justification

+ *

* The collateral mechanism is an important feature that has been designed to ensure * successful smart contract execution. + * * Collateral is used to guarantee that nodes are compensated for their work in case phase-2 validation fails. * Thus, collateral is the monetary guarantee a user gives to assure that the contract has been carefully designed - * and thoroughly tested. Collateral amount is specified at the time of constructing the transaction. - * Not directly, but by adding collateral inputs to the transaction. The total balance in the UTXOs + * and thoroughly tested. + * + * Collateral amount is specified at the time of constructing the transaction. + * Not directly, but by adding collateral inputs to the transaction. + * + * The total balance in the UTXOs * corresponding to these specially marked inputs is the transaction’s collateral amount. + * * If the user fulfills the conditions of the guarantee, and a contract gets executed, the collateral is safe. + *

* @see * https://docs.cardano.org/smart-contracts/plutus/collateral-mechanism */ @@ -264,9 +279,12 @@ export interface BuildCreateContractTxRequest { /** * Marlowe Tags are stored as Metadata within the Transaction Metadata under the top-level Marlowe Reserved Key (`1564`). * Tags allows to Query created Marlowe Contracts via {@link index.RestClient#getContracts | Get contracts } - * @remarks + * + *

Properties

+ * * 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 @@ -279,7 +297,8 @@ export interface BuildCreateContractTxRequest { tags?: Tags; /** * Cardano Metadata about the contract creation. - * @remarks + *

Properties

+ *

* Metadata can be expressed as a JSON object with some restrictions: * - All top-level keys must be integers between 0 and 2^64 - 1. * - Each metadata value is tagged with its type. @@ -294,7 +313,7 @@ export interface BuildCreateContractTxRequest { * - 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 @@ -303,19 +322,120 @@ export interface BuildCreateContractTxRequest { /** * 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 Contracts over Cardano is about creating UTxO entries on the Ledger. + * + *

Justification

+ *

Creating a Marlowe Contracts 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 (mininmumLovelaceUTxODeposit)" that adjust - * the value (in ADA) of each UTxO has been added. The more the UTxOs entries are big in size, the more the value of minimum - * of ADAs needs to be contained. + * the value (in ADA) of each UTxO has been added. + * + * The more the UTxOs entries are big in size, the more the value of minimum + * of ADAs needs to be contained.

* @see * https://docs.cardano.org/native-tokens/minimum-ada-value-requirement */ mininmumLovelaceUTxODeposit?: number; - // TODO: Comment this and improve the generated type (currently `string | {}`) - roles?: RolesConfig; + /** + * @experimental + * Thread Roles are a details of implementation within the runtime. It allows provide a custom name + * if the thread role name is conflicting with other role names used. + * @default + * - the Thread Role Name is "" by default. + */ + threadRoleName?: RoleName; + + /** + * Role Token Configuration for the contract passed in the `contractOrSourceId` field. + * + *

Prerequisite

+ *

+ * Participants ({@link @marlowe.io/language-core-v1!index.Party | Party}) in a Marlowe Contract can be expressed in 2 ways: + * + * 1. **By Adressses** : Addresses are directly defined within the Marlowe Contract and no configuration are necessary in that context. + * 2. **By Roles** : Defined by {@link @marlowe.io/language-core-v1!index.RoleName | RoleNames} within the Marlowe Contract, they have to match a Token Name when created in Cardano. This field `rolesConfiguration` is about configuring this use case + *

+ * + *

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 knowned at the creation and therfore we consider the participation as being closed. + * - **Openly** (Open Roles) : Whoever applies an input (IDeposit or IChoice) on the contract `contractOrSourceId` 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.mkUseMintedRoleTokens} + * - {@link @marlowe.io/runtime-rest-client!contract.mkMintOpenRoleToken} + * - {@link @marlowe.io/runtime-rest-client!contract.mkMintClosedRoleToken} + * + * @remarks + * - The Distribution can be a mix of Closed and Open Role Tokens configuration. See examples below. + *

+ * + * @example + * + * ```ts + * ////////////// + * // #1 - Mint within the Runtime + * ////////////// + * const anAddressBech32 = "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja" + * const aMintingConfiguration = + * { "closed_Role_A_NFT" : mkMintClosedRoleToken(anAddressBech32) + * , "closed_Role_B_FT" : + * mkMintClosedRoleToken( + * anAddressBech32, + * 5, // Quantities + * { "name": "closed_Role_B_FT Marlowe Role Token", + "description": "These are metadata for closedRoleB", + * image": "ipfs://QmaQMH7ybS9KmdYQpa4FMtAhwJH5cNaacpg4fTwhfPvcwj", + * "mediaType": "image/png", + * "files": [ + * { + * "name": "icon-1000", + * "mediaType": "image/webp", + * "src": "ipfs://QmUbvavFxGSSEo3ipQf7rjrELDvXHDshWkHZSpV8CVdSE5" + * } + * ] + * }) + * , "open_Role_C" : mkMintOpenRoleToken() + * , "open_Role_D" : mkMintOpenRoleToken( + * 2, // Quantities + * { "name": "open_Role_D Marlowe Role Token", + "description": "These are metadata for closedRoleB", + * image": "ipfs://QmaQMH7ybS9KmdYQpa4FMtAhwJH5cNaacpg4fTwhfPvcwj", + * "mediaType": "image/png", + * "files": [ + * { + * "name": "icon-1000", + * "mediaType": "image/webp", + * "src": "ipfs://QmUbvavFxGSSEo3ipQf7rjrELDvXHDshWkHZSpV8CVdSE5" + * } + * ] + * }) + * } + * + * ////////////// + * // #2 Use Minted Roles Tokens + * const aUseMintedRoleTokensConfiguration = + * mkUseMintedRoleTokens( + * "e68f1cea19752d1292b4be71b7f5d2b3219a15859c028f7454f66cdf", + * ["role_A","role_C"] + * ) + * ``` + * + * @see + * - {@link @marlowe.io/runtime-rest-client!contract.mkUseMintedRoleTokens} + * - {@link @marlowe.io/runtime-rest-client!contract.mkMintOpenRoleToken} + * - {@link @marlowe.io/runtime-rest-client!contract.mkMintClosedRoleToken} + * - Open Roles Runtime Implementation : https://github.com/input-output-hk/marlowe-cardano/blob/main/marlowe-runtime/doc/open-roles.md + */ + rolesConfiguration?: RolesConfiguration; /** * The Marlowe validator version to use. @@ -323,7 +443,7 @@ export interface BuildCreateContractTxRequest { version: MarloweVersion; } -export type POST = ( +export type BuildCreateContractTxEndpoint = ( postContractsRequest: PostContractsRequest, addressesAndCollaterals: AddressesAndCollaterals, stakeAddress?: StakeAddressBech32 @@ -343,7 +463,7 @@ export const PostContractsRequest = t.intersection([ tags: TagsGuard, metadata: Metadata, }), - t.partial({ roles: RolesConfig }), + t.partial({ roles: RolesConfigurationGuard }), t.partial({ minUTxODeposit: t.number }), ]); @@ -391,7 +511,9 @@ export const PostResponse = t.type({ /** * @see {@link https://docs.marlowe.iohk.io/api/create-contracts} */ -export const postViaAxios: (axiosInstance: AxiosInstance) => POST = +export const postViaAxios: ( + axiosInstance: AxiosInstance +) => BuildCreateContractTxEndpoint = (axiosInstance) => (postContractsRequest, addressesAndCollaterals, stakeAddress) => pipe( diff --git a/packages/runtime/client/rest/src/contract/guards.ts b/packages/runtime/client/rest/src/contract/guards.ts new file mode 100644 index 00000000..93c1faa0 --- /dev/null +++ b/packages/runtime/client/rest/src/contract/guards.ts @@ -0,0 +1,27 @@ +/** + * This module offers {@link !io-ts-usage | dynamic type guards} for JSON schemas as specified in Runtime Rest API + ``` + import * as G from "@marlowe/language-core-v1/guards" + const jsonObject = JSON.parse(fileContents) + + if (G.Contract.is(jsonObject)) { + // The jsonObject respects the JSON schema for Contract + } else { + // The jsonObject does not respect the JSON schema for Contract + } + ``` + @packageDocumentation + */ + +export { + ClosedRoleGuard as ClosedRole, + OpenRoleGuard as OpenRole, + OpenessGuard as Openess, + UsePolicyWithClosedRoleTokensGuard as UsePolicyWithClosedRoleTokens, + UsePolicyWithOpenRoleTokensGuard as UsePolicyWithOpenRoleTokens, + TokenMetadataFileGuard as TokenMetadataFile, + TokenMetadataGuard as TokenMetadata, + RoleTokenConfigurationGuard as RoleTokenConfiguration, + RolesConfigurationGuard as RolesConfiguration, + AddressBech32Guard as AddressBech32, +} from "./rolesConfigurations.js"; diff --git a/packages/runtime/client/rest/src/contract/index.ts b/packages/runtime/client/rest/src/contract/index.ts index 914c945d..95067f49 100644 --- a/packages/runtime/client/rest/src/contract/index.ts +++ b/packages/runtime/client/rest/src/contract/index.ts @@ -13,7 +13,26 @@ export { ContractHeader } from "./header.js"; export { ContractDetails } from "./details.js"; -export { RolesConfig } from "./role.js"; +export { + mkUseMintedRoleTokens, + mkMintClosedRoleToken, + mkMintOpenRoleToken, + AddressBech32Brand, + AddressBech32, + mkOpenRole, + ClosedRole, + OpenRole, + Openess, + UsePolicyWithClosedRoleTokens, + UsePolicyWithOpenRoleTokens, + MintRolesTokens, + TokenMetadataFile, + TokenMetadata, + Recipient, + TokenQuantity, + RoleTokenConfiguration, + RolesConfiguration, +} from "./rolesConfigurations.js"; export { GetContractsResponse, GetContractsRequest, @@ -27,4 +46,7 @@ export { TransactionsRange, GetTransactionsForContractResponse, } from "./transaction/endpoints/collection.js"; + export { TransactionDetails } from "./transaction/details.js"; + +export { TransactionTextEnvelope } from "./transaction/endpoints/collection.js"; diff --git a/packages/runtime/client/rest/src/contract/role.ts b/packages/runtime/client/rest/src/contract/role.ts deleted file mode 100644 index 2b5f1d99..00000000 --- a/packages/runtime/client/rest/src/contract/role.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as t from "io-ts/lib/index.js"; -import { optionFromNullable } from "io-ts-types"; -import { PolicyId } from "@marlowe.io/language-core-v1"; -import { AddressBech32 } from "@marlowe.io/runtime-core"; - -export type RoleName = string; -export const RoleName = t.string; - -export type UsePolicy = t.TypeOf; -export const UsePolicy = PolicyId; - -export type RoleTokenSimple = t.TypeOf; -export const RoleTokenSimple = AddressBech32; - -export type TokenMetadataFile = t.TypeOf; -export const TokenMetadataFile = t.type({ - name: t.string, - src: t.string, - mediaType: t.string, -}); - -export type TokenMetadata = t.TypeOf; -export const TokenMetadata = t.type({ - name: optionFromNullable(t.string), - image: optionFromNullable(t.string), - mediaType: t.string, - description: t.string, - files: t.array(TokenMetadataFile), -}); - -export type RoleTokenAdvanced = t.TypeOf; -export const RoleTokenAdvanced = t.type({ - address: AddressBech32, - metadata: TokenMetadata, -}); - -export type RoleTokenConfig = t.TypeOf; -export const RoleTokenConfig = t.union([RoleTokenSimple, RoleTokenAdvanced]); - -export type Mint = t.TypeOf; -export const Mint = t.record(RoleName, RoleTokenConfig); - -/** - * @category Endpoint : Build Create Contract Tx - */ -export type RolesConfig = t.TypeOf; - -export const RolesConfig = t.union([UsePolicy, Mint]); diff --git a/packages/runtime/client/rest/src/contract/rolesConfigurations.ts b/packages/runtime/client/rest/src/contract/rolesConfigurations.ts new file mode 100644 index 00000000..03995583 --- /dev/null +++ b/packages/runtime/client/rest/src/contract/rolesConfigurations.ts @@ -0,0 +1,250 @@ +import * as t from "io-ts/lib/index.js"; + +import { PolicyId, RoleName } from "@marlowe.io/language-core-v1"; + +import * as G from "@marlowe.io/language-core-v1/guards"; + +/** + * @category Roles Configuration + */ +export interface AddressBech32Brand { + readonly AddressBech32: unique symbol; +} + +export const AddressBech32Guard = t.brand( + t.string, + (s): s is t.Branded => true, + "AddressBech32" +); + +/** + * Cardano Address in a Bech32 format + * @category Roles Configuration + */ +export type AddressBech32 = t.TypeOf; + +/** + * Definition a of Closed Role Tlken + * @remarks + * - It is only defined by the Address where the token is distributed + * @category Roles Configuration + */ +export type ClosedRole = AddressBech32; +export const ClosedRoleGuard: t.Type = AddressBech32Guard; + +/** + * Definition of an Open Role Token + * @category Roles Configuration + */ +export type OpenRole = "OpenRole"; +export const OpenRoleGuard = t.literal("OpenRole"); + +/** + * Construction of an Open Role Token + * @category Roles Configuration + */ +export const mkOpenRole = "OpenRole"; + +/** + * Definition of the Openness of a Role Token + * @category Roles Configuration + */ +export type Openess = ClosedRole | OpenRole; +export const OpenessGuard: t.Type = t.union([ + ClosedRoleGuard, + OpenRoleGuard, +]); + +/** + * @category Roles Configuration + */ +export type UsePolicyWithClosedRoleTokens = PolicyId; + +export const UsePolicyWithClosedRoleTokensGuard: t.Type = + G.PolicyId; + +/** + * @category Roles Configuration + */ +export interface UsePolicyWithOpenRoleTokens { + script: OpenRole; + policyId: PolicyId; + openRoleNames: RoleName[]; +} +export const UsePolicyWithOpenRoleTokensGuard: t.Type = + t.type({ + script: OpenRoleGuard, + policyId: G.PolicyId, + openRoleNames: t.array(G.RoleName), + }); + +/** + * @category Roles Configuration + */ +export interface TokenMetadataFile { + name: string; + src: string; + mediaType: string; +} + +export const TokenMetadataFileGuard: t.Type = t.type({ + name: t.string, + src: t.string, + mediaType: t.string, +}); + +/** + * @category Roles Configuration + * TODO : Which CIP are we following here ? + * @see + * - https://developers.cardano.org/docs/native-tokens/minting-nfts/ + * - https://docs.nmkr.io/nmkr-studio/token/metadata/metadata-standard-for-fungible-tokens + */ +export interface TokenMetadata { + name?: string; + image?: string; + mediaType: string; + description: string; + files: TokenMetadataFile[]; +} + +export const TokenMetadataGuard: t.Type = t.intersection([ + t.type({ + mediaType: t.string, + description: t.string, + files: t.array(TokenMetadataFileGuard), + }), + t.partial({ name: t.string }), + t.partial({ image: t.string }), +]); + +/** + * @category Roles Configuration + */ +export interface ClosedNFTWithMetadata { + address: AddressBech32; + metadata?: TokenMetadata; +} + +/** + * @category Roles Configuration + */ +export type Recipient = Openess; + +export const RecipientGuard: t.Type = t.union([ + OpenRoleGuard, + ClosedRoleGuard, +]); + +/** + * @category Roles Configuration + */ +export type TokenQuantity = bigint; + +export const TokenQuantityGuard: t.Type = t.bigint; + +/** + * @category Roles Configuration + */ +export interface RoleTokenConfiguration { + recipients: + | { [x: string & t.Brand]: TokenQuantity } + | { OpenRole: TokenQuantity }; + metadata?: TokenMetadata; +} + +export const RoleTokenConfigurationGuard: t.Type = + t.intersection([ + t.type({ + recipients: t.union([ + t.record(OpenRoleGuard, TokenQuantityGuard), + t.record(ClosedRoleGuard, TokenQuantityGuard), + ]), + }), + t.partial({ metadata: TokenMetadataGuard }), + ]); + +/** + * @category Roles Configuration + */ +export type MintRolesTokens = { [x: RoleName]: RoleTokenConfiguration }; + +export const MintRolesTokensGuard: t.Type = t.record( + G.RoleName, + RoleTokenConfigurationGuard +); + +/** + * Defines how to configure Roles over Cardano at the creation of a Marlowe Contract. + * @see + * Smart Constructors are available to ease the configuration: + * - {@link @marlowe.io/runtime-rest-client!contract.mkUseMintedRoleTokens} + * - {@link @marlowe.io/runtime-rest-client!contract.mkMintOpenRoleToken} + * - {@link @marlowe.io/runtime-rest-client!contract.mkMintClosedRoleToken} + * @category Endpoint : Build Create Contract Tx + * @category Roles Configuration + */ +export type RolesConfiguration = + | UsePolicyWithClosedRoleTokens + | UsePolicyWithOpenRoleTokens + | MintRolesTokens; + +export const RolesConfigurationGuard = t.union([ + UsePolicyWithClosedRoleTokensGuard, + UsePolicyWithOpenRoleTokensGuard, + MintRolesTokensGuard, +]); + +/** + * Configure Roles using tokens previously Minted. These Role Tokens are already defined (via an NFT platform, cardano-cli, another Marlowe Contract Created, etc.. ) + * @param policyId The policy Id of All the token roles defined in the Marlowe Contract DSL + * @param openRoleNames defines all the Roles to be Open (Others will be Closed) + * @remarks + * It is under the user's responsability to create and distribute properly these role tokens + * - Make sure all the Token Name are minted and match all the Role Names defined in the contract + * - Depending on the Marlowe Contract logic, make sure the tokens are distributed to the right wallet + * When using Open Role Tokens + * - Thread Role Token needs to me minted when using Open Roles (by default threadRoleName = "") + * - see {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } + * @category Endpoint : Build Create Contract Tx + * @category Roles Configuration + */ +export const mkUseMintedRoleTokens = ( + policyId: PolicyId, + openRoleNames?: RoleName[] +): RolesConfiguration => + openRoleNames + ? { script: mkOpenRole, policyId: policyId, openRoleNames: openRoleNames } + : (policyId as UsePolicyWithClosedRoleTokens); + +/** + * Configure the minting of a Closed Role Token. + * @param address where to distribute the token that will be mint + * @param quantity Quantity of the Closed Role Token (by Default an NFT (==1)) + * @param metadata Token Metadata of the Token + * @category Endpoint : Build Create Contract Tx + * @category Roles Configuration + */ +export const mkMintClosedRoleToken: ( + address: AddressBech32, + quantity?: TokenQuantity, + metadata?: TokenMetadata +) => RoleTokenConfiguration = (address, quantity, metadata) => ({ + recipients: { [address]: quantity ? quantity : 1n }, + metadata: metadata, +}); + +/** + * Configure the minting of an Open Role Token. + * @param quantity Quantity of the Closed Role Token (by Default an NFT (==1)) + * @param metadata Token Metadata of the Token + * @category Endpoint : Build Create Contract Tx + * @category Roles Configuration + */ +export const mkMintOpenRoleToken: ( + quantity?: TokenQuantity, + metadata?: TokenMetadata +) => RoleTokenConfiguration = (quantity, metadata) => ({ + recipients: { [mkOpenRole]: quantity ? quantity : 1n }, + metadata: metadata, +}); diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index ed84827a..94803946 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -43,18 +43,6 @@ import { TransactionDetails } from "./contract/transaction/details.js"; import { CreateContractSourcesResponse } from "./contract/endpoints/sources.js"; // import curlirize from 'axios-curlirize'; -// TODO: DELETE -// export * from "./contract/index.js"; -// export * from "./withdrawal/index.js"; -// export * from "./payout/index.js"; -// TODO: Revisit -export { Assets, Tokens } from "./payout/index.js"; -export { RolesConfig } from "./contract/index.js"; -// Jamie Straw suggestion: 20 endpoints -// Runtime Rest API docs: 18 endpoints (missing, getPayouts and getPayoutById, I assume version 0.0.5) -// Current TS-SDK: 16 endpoints -// https://docs.marlowe.iohk.io/api -// openapi.json on main (RC 0.0.5): 20 endpoints /** * The RestClient offers a simple abstraction for the {@link https://docs.marlowe.iohk.io/api/ | Marlowe Runtime REST API} endpoints. * You can create an instance of the RestClient using the {@link mkRestClient} function. @@ -269,7 +257,9 @@ export function mkRestClient(baseURL: string): RestClient { ...(request.mininmumLovelaceUTxODeposit && { minUTxODeposit: request.mininmumLovelaceUTxODeposit, }), - ...(request.roles && { roles: request.roles }), + ...(request.rolesConfiguration && { + roles: request.rolesConfiguration, + }), }; const addressesAndCollaterals = { changeAddress: request.changeAddress, @@ -463,7 +453,7 @@ export interface ContractsAPI { /** * @see {@link https://docs.marlowe.iohk.io/api/create-contracts} */ - post: Contracts.POST; + post: Contracts.BuildCreateContractTxEndpoint; contract: { /** * Get a single contract by id diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts index 70a66078..30063dcb 100644 --- a/packages/runtime/lifecycle/src/api.ts +++ b/packages/runtime/lifecycle/src/api.ts @@ -9,7 +9,8 @@ import { Tags, TxId, } from "@marlowe.io/runtime-core"; -import { RestDI, RolesConfig } from "@marlowe.io/runtime-rest-client"; +import { 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 } from "@marlowe.io/language-core-v1"; import { Next } from "@marlowe.io/language-core-v1/next"; @@ -29,7 +30,7 @@ export type ContractsDI = WalletDI & RestDI; export type CreateContractRequest = { contract: Contract; - roles?: RolesConfig; + roles?: RolesConfiguration; tags?: Tags; metadata?: Metadata; mininmumLovelaceUTxODeposit?: number; diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts index 43c8244d..f54bd1c7 100644 --- a/packages/runtime/lifecycle/src/generic/contracts.ts +++ b/packages/runtime/lifecycle/src/generic/contracts.ts @@ -23,10 +23,13 @@ import { import { FPTSRestAPI } from "@marlowe.io/runtime-rest-client"; import { DecodingError } from "@marlowe.io/adapter/codec"; -import { TransactionTextEnvelope } from "@marlowe.io/runtime-rest-client/contract/transaction/endpoints/collection"; + import { Next, noNext } from "@marlowe.io/language-core-v1/next"; import { isNone, none, Option } from "fp-ts/lib/Option.js"; -import { ContractsRange } from "@marlowe.io/runtime-rest-client/contract/index"; +import { + ContractsRange, + TransactionTextEnvelope, +} from "@marlowe.io/runtime-rest-client/contract"; export function mkContractLifecycle( wallet: WalletAPI, diff --git a/packages/runtime/lifecycle/src/generic/payouts.ts b/packages/runtime/lifecycle/src/generic/payouts.ts index 82e7faff..7dff0800 100644 --- a/packages/runtime/lifecycle/src/generic/payouts.ts +++ b/packages/runtime/lifecycle/src/generic/payouts.ts @@ -19,7 +19,7 @@ import { import { FPTSRestAPI } from "@marlowe.io/runtime-rest-client"; -import * as Rest from "@marlowe.io/runtime-rest-client"; +import * as RestPayout from "@marlowe.io/runtime-rest-client/payout"; import { DecodingError } from "@marlowe.io/adapter/codec"; import { stringify } from "json-bigint"; @@ -163,12 +163,12 @@ const fetchWithdrawnPayoutsFpTs: ( ) ); -const convertAsset: (assets: Rest.Assets) => Assets = (restAssets) => ({ +const convertAsset: (assets: RestPayout.Assets) => Assets = (restAssets) => ({ lovelaces: restAssets.lovelace, tokens: convertTokens(restAssets.tokens), }); -const convertTokens: (tokens: Rest.Tokens) => Tokens = (restTokens) => +const convertTokens: (tokens: RestPayout.Tokens) => Tokens = (restTokens) => Object.entries(restTokens) .map(([policyId, x]) => Object.entries(x).map(([assetName, quantity]) => From 83ec5e5847713df24e8e74a3212df57748925e29 Mon Sep 17 00:00:00 2001 From: nhenin Date: Mon, 4 Dec 2023 19:19:15 +0100 Subject: [PATCH 07/13] update runtime.lifecycle createContract API --- examples/get-my-contract-ids-flow/index.html | 2 +- examples/run-lite/index.html | 4 +- examples/survey-workshop/participant/index.js | 2 +- examples/vesting-flow/index.html | 2 +- flake.lock | 6 +- jsdelivr-npm-importmap.js | 113 ++++------- .../client/rest/src/contract/details.ts | 2 +- .../rest/src/contract/endpoints/collection.ts | 25 ++- .../runtime/client/rest/src/contract/index.ts | 9 +- .../rest/src/contract/rolesConfigurations.ts | 52 ++--- packages/runtime/client/rest/src/index.ts | 2 +- packages/runtime/lifecycle/src/api.ts | 182 +++++++++++++++++- .../runtime/lifecycle/src/browser/index.ts | 10 +- .../lifecycle/src/generic/contracts.ts | 95 +++++---- .../runtime/lifecycle/src/generic/payouts.ts | 25 ++- .../runtime/lifecycle/src/generic/runtime.ts | 9 +- packages/runtime/lifecycle/src/index.ts | 7 +- .../runtime/lifecycle/src/nodejs/index.ts | 25 +++ .../test/examples/swap.ada.token.e2e.spec.ts | 23 ++- .../test/generic/contracts.e2e.spec.ts | 4 +- .../test/generic/payouts.e2e.spec.ts | 14 +- 21 files changed, 410 insertions(+), 203 deletions(-) create mode 100644 packages/runtime/lifecycle/src/nodejs/index.ts diff --git a/examples/get-my-contract-ids-flow/index.html b/examples/get-my-contract-ids-flow/index.html index 3cc18051..e18a8109 100644 --- a/examples/get-my-contract-ids-flow/index.html +++ b/examples/get-my-contract-ids-flow/index.html @@ -41,7 +41,7 @@ window.createContract = async () => { const address = await window.runtimeLifeCycle.wallet.getChangeAddress(); return runtimeLifeCycle.contracts.createContract({ - contract: mkContract(address, Date.now() + 1000 * 60 * 10), + contractOrSourceId: mkContract(address, Date.now() + 1000 * 60 * 10), }); }; diff --git a/examples/run-lite/index.html b/examples/run-lite/index.html index c56d0165..1e64c0ac 100644 --- a/examples/run-lite/index.html +++ b/examples/run-lite/index.html @@ -108,7 +108,9 @@

Console

walletName, }); - const contractId = await runtime.contracts.createContract({ contract }); + const contractId = await runtime.contracts.createContract({ + contractOrSourceId: contract, + }); log("Contract created with id: " + contractId); setContractIdIndicator(contractId); } diff --git a/examples/survey-workshop/participant/index.js b/examples/survey-workshop/participant/index.js index 29c7f167..d081c565 100644 --- a/examples/survey-workshop/participant/index.js +++ b/examples/survey-workshop/participant/index.js @@ -124,7 +124,7 @@ async function createContract( const lifecycle = await H.getLifecycle(); const [contractId] = await lifecycle.contracts.createContract({ - contract, + contractOrSourceId: contract, tags: { MarloweSurvey: "test 1" }, }); diff --git a/examples/vesting-flow/index.html b/examples/vesting-flow/index.html index b014da5d..03498256 100644 --- a/examples/vesting-flow/index.html +++ b/examples/vesting-flow/index.html @@ -116,7 +116,7 @@

Console

const [contractId, txIdCreated] = await runtimeLifeCycle.contracts.createContract({ - contract: vestingContract, + contractOrSourceId: vestingContract, tags: { [dappId]: { scheme: request.scheme } }, }); log( diff --git a/flake.lock b/flake.lock index da1c7987..70f303e0 100644 --- a/flake.lock +++ b/flake.lock @@ -119,11 +119,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701787589, - "narHash": "sha256-ce+oQR4Zq9VOsLoh9bZT8Ip9PaMLcjjBUHVPzW5d7Cw=", + "lastModified": 1701697687, + "narHash": "sha256-dLLE5wQBVv+pIb4bWmKFSw2DvLVyuEk0F7ng6hpZPSU=", "owner": "numtide", "repo": "devshell", - "rev": "44ddedcbcfc2d52a76b64fb6122f209881bd3e1e", + "rev": "c3bd77911391eb1638af6ce773de86da57ee6df5", "type": "github" }, "original": { diff --git a/jsdelivr-npm-importmap.js b/jsdelivr-npm-importmap.js index b38ef5d3..020b33c9 100644 --- a/jsdelivr-npm-importmap.js +++ b/jsdelivr-npm-importmap.js @@ -1,75 +1,44 @@ const importMap = { - imports: { - "@marlowe.io/adapter": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/adapter.js", - "@marlowe.io/adapter/assoc-map": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/assoc-map.js", - "@marlowe.io/adapter/codec": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/codec.js", - "@marlowe.io/adapter/deep-equal": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/deep-equal.js", - "@marlowe.io/adapter/file": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/file.js", - "@marlowe.io/adapter/fp-ts": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/fp-ts.js", - "@marlowe.io/adapter/http": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/http.js", - "@marlowe.io/adapter/io-ts": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/io-ts.js", - "@marlowe.io/adapter/time": - "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/time.js", - "@marlowe.io/language-core-v1": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/language-core-v1.js", - "@marlowe.io/language-core-v1/guards": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/guards.js", - "@marlowe.io/language-core-v1/next": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/next.js", - "@marlowe.io/language-core-v1/playground-v1": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/playground-v1.js", - "@marlowe.io/language-core-v1/semantics": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/semantics.js", - "@marlowe.io/language-core-v1/version": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/version.js", - "@marlowe.io/language-examples": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.3.0-beta-rc1/dist/bundled/esm/language-examples.js", - "@marlowe.io/language-specification-client": - "https://cdn.jsdelivr.net/npm/@marlowe.io/language-specification-client@0.3.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.3.0-beta-rc1/dist/bundled/esm/token-metadata-client.js", - "@marlowe.io/wallet": - "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/wallet.js", - "@marlowe.io/wallet/api": - "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/api.js", - "@marlowe.io/wallet/browser": - "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/browser.js", - "@marlowe.io/wallet/lucid": - "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/lucid.js", - "@marlowe.io/wallet/nodejs": - "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/nodejs.js", - "@marlowe.io/runtime-rest-client": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/runtime-rest-client.js", - "@marlowe.io/runtime-rest-client/transaction": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/transaction.js", - "@marlowe.io/runtime-rest-client/withdrawal": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/withdrawal.js", - "@marlowe.io/runtime-core": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-core@0.3.0-beta-rc1/dist/bundled/esm/runtime-core.js", - "@marlowe.io/runtime-lifecycle": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/runtime-lifecycle.js", - "@marlowe.io/runtime-lifecycle/api": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/api.js", - "@marlowe.io/runtime-lifecycle/browser": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/browser.js", - "@marlowe.io/runtime-lifecycle/generic": - "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/generic.js", - "@marlowe.io/marlowe-object": - "https://cdn.jsdelivr.net/npm/@marlowe.io/marlowe-object@0.3.0-beta-rc1/dist/bundled/esm/marlowe-object.js", - "@marlowe.io/marlowe-object/guards": - "https://cdn.jsdelivr.net/npm/@marlowe.io/marlowe-object@0.3.0-beta-rc1/dist/bundled/esm/guards.js", - "lucid-cardano": "https://unpkg.com/lucid-cardano@0.10.7/web/mod.js", - }, + "imports": { + "@marlowe.io/adapter": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/adapter.js", + "@marlowe.io/adapter/assoc-map": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/assoc-map.js", + "@marlowe.io/adapter/codec": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/codec.js", + "@marlowe.io/adapter/deep-equal": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/deep-equal.js", + "@marlowe.io/adapter/file": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/file.js", + "@marlowe.io/adapter/fp-ts": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/fp-ts.js", + "@marlowe.io/adapter/http": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/http.js", + "@marlowe.io/adapter/io-ts": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/io-ts.js", + "@marlowe.io/adapter/time": "https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta-rc1/dist/bundled/esm/time.js", + "@marlowe.io/language-core-v1": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/language-core-v1.js", + "@marlowe.io/language-core-v1/guards": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/guards.js", + "@marlowe.io/language-core-v1/next": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/next.js", + "@marlowe.io/language-core-v1/playground-v1": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/playground-v1.js", + "@marlowe.io/language-core-v1/semantics": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/semantics.js", + "@marlowe.io/language-core-v1/version": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta-rc1/dist/bundled/esm/version.js", + "@marlowe.io/language-examples": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-examples@0.3.0-beta-rc1/dist/bundled/esm/language-examples.js", + "@marlowe.io/language-specification-client": "https://cdn.jsdelivr.net/npm/@marlowe.io/language-specification-client@0.3.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.3.0-beta-rc1/dist/bundled/esm/token-metadata-client.js", + "@marlowe.io/wallet": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/wallet.js", + "@marlowe.io/wallet/api": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/api.js", + "@marlowe.io/wallet/browser": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/browser.js", + "@marlowe.io/wallet/lucid": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/lucid.js", + "@marlowe.io/wallet/nodejs": "https://cdn.jsdelivr.net/npm/@marlowe.io/wallet@0.3.0-beta-rc1/dist/bundled/esm/nodejs.js", + "@marlowe.io/runtime-rest-client": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/runtime-rest-client.js", + "@marlowe.io/runtime-rest-client/contract": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/contract.js", + "@marlowe.io/runtime-rest-client/payout": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/payout.js", + "@marlowe.io/runtime-rest-client/transaction": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/transaction.js", + "@marlowe.io/runtime-rest-client/withdrawal": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-rest-client@0.3.0-beta-rc1/dist/bundled/esm/withdrawal.js", + "@marlowe.io/runtime-core": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-core@0.3.0-beta-rc1/dist/bundled/esm/runtime-core.js", + "@marlowe.io/runtime-lifecycle": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/runtime-lifecycle.js", + "@marlowe.io/runtime-lifecycle/api": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/api.js", + "@marlowe.io/runtime-lifecycle/browser": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/browser.js", + "@marlowe.io/runtime-lifecycle/generic": "https://cdn.jsdelivr.net/npm/@marlowe.io/runtime-lifecycle@0.3.0-beta-rc1/dist/bundled/esm/generic.js", + "@marlowe.io/marlowe-object": "https://cdn.jsdelivr.net/npm/@marlowe.io/marlowe-object@0.3.0-beta-rc1/dist/bundled/esm/marlowe-object.js", + "@marlowe.io/marlowe-object/guards": "https://cdn.jsdelivr.net/npm/@marlowe.io/marlowe-object@0.3.0-beta-rc1/dist/bundled/esm/guards.js", + "lucid-cardano": "https://unpkg.com/lucid-cardano@0.10.7/web/mod.js" + } }; -const im = document.createElement("script"); -im.type = "importmap"; +const im = document.createElement('script'); +im.type = 'importmap'; im.textContent = JSON.stringify(importMap); -document.currentScript.after(im); +document.currentScript.after(im); \ No newline at end of file diff --git a/packages/runtime/client/rest/src/contract/details.ts b/packages/runtime/client/rest/src/contract/details.ts index 69993f96..9c4d1fc2 100644 --- a/packages/runtime/client/rest/src/contract/details.ts +++ b/packages/runtime/client/rest/src/contract/details.ts @@ -32,7 +32,7 @@ export const Payout = t.type({ */ payoutId: TxOutRef, /** - * The {@link RoleName} of the participant that has the unclaimed Payout. + * The {@link @marlowe.io/language-core-v1!index.RoleName | Role Name} of the participant that has the unclaimed Payout. */ role: G.RoleName, }); diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index 69eef071..b03bbb34 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -370,9 +370,8 @@ export interface BuildCreateContractTxRequest { * - **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.mkUseMintedRoleTokens} - * - {@link @marlowe.io/runtime-rest-client!contract.mkMintOpenRoleToken} - * - {@link @marlowe.io/runtime-rest-client!contract.mkMintClosedRoleToken} + * - {@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. @@ -382,13 +381,13 @@ export interface BuildCreateContractTxRequest { * * ```ts * ////////////// - * // #1 - Mint within the Runtime + * // #1 - Mint Role Tokens * ////////////// * const anAddressBech32 = "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja" * const aMintingConfiguration = - * { "closed_Role_A_NFT" : mkMintClosedRoleToken(anAddressBech32) + * { "closed_Role_A_NFT" : mintRole(anAddressBech32) * , "closed_Role_B_FT" : - * mkMintClosedRoleToken( + * mintRole( * anAddressBech32, * 5, // Quantities * { "name": "closed_Role_B_FT Marlowe Role Token", @@ -403,11 +402,12 @@ export interface BuildCreateContractTxRequest { * } * ] * }) - * , "open_Role_C" : mkMintOpenRoleToken() - * , "open_Role_D" : mkMintOpenRoleToken( + * , "open_Role_C" : mintRole(openRole) + * , "open_Role_D" : mintRole( + * openRole, * 2, // Quantities * { "name": "open_Role_D Marlowe Role Token", - "description": "These are metadata for closedRoleB", + * "description": "These are metadata for closedRoleB", * image": "ipfs://QmaQMH7ybS9KmdYQpa4FMtAhwJH5cNaacpg4fTwhfPvcwj", * "mediaType": "image/png", * "files": [ @@ -423,16 +423,15 @@ export interface BuildCreateContractTxRequest { * ////////////// * // #2 Use Minted Roles Tokens * const aUseMintedRoleTokensConfiguration = - * mkUseMintedRoleTokens( + * useMintedRoles( * "e68f1cea19752d1292b4be71b7f5d2b3219a15859c028f7454f66cdf", * ["role_A","role_C"] * ) * ``` * * @see - * - {@link @marlowe.io/runtime-rest-client!contract.mkUseMintedRoleTokens} - * - {@link @marlowe.io/runtime-rest-client!contract.mkMintOpenRoleToken} - * - {@link @marlowe.io/runtime-rest-client!contract.mkMintClosedRoleToken} + * - {@link @marlowe.io/runtime-rest-client!contract.useMintedRoles} + * - {@link @marlowe.io/runtime-rest-client!contract.mintRole} * - Open Roles Runtime Implementation : https://github.com/input-output-hk/marlowe-cardano/blob/main/marlowe-runtime/doc/open-roles.md */ rolesConfiguration?: RolesConfiguration; diff --git a/packages/runtime/client/rest/src/contract/index.ts b/packages/runtime/client/rest/src/contract/index.ts index 95067f49..d21740e6 100644 --- a/packages/runtime/client/rest/src/contract/index.ts +++ b/packages/runtime/client/rest/src/contract/index.ts @@ -14,15 +14,14 @@ export { ContractHeader } from "./header.js"; export { ContractDetails } from "./details.js"; export { - mkUseMintedRoleTokens, - mkMintClosedRoleToken, - mkMintOpenRoleToken, + useMintedRoles, + mintRole, AddressBech32Brand, AddressBech32, - mkOpenRole, + openRole, ClosedRole, OpenRole, - Openess, + Openness, UsePolicyWithClosedRoleTokens, UsePolicyWithOpenRoleTokens, MintRolesTokens, diff --git a/packages/runtime/client/rest/src/contract/rolesConfigurations.ts b/packages/runtime/client/rest/src/contract/rolesConfigurations.ts index 03995583..b87ff721 100644 --- a/packages/runtime/client/rest/src/contract/rolesConfigurations.ts +++ b/packages/runtime/client/rest/src/contract/rolesConfigurations.ts @@ -43,14 +43,14 @@ export const OpenRoleGuard = t.literal("OpenRole"); * Construction of an Open Role Token * @category Roles Configuration */ -export const mkOpenRole = "OpenRole"; +export const openRole = "OpenRole"; /** * Definition of the Openness of a Role Token * @category Roles Configuration */ -export type Openess = ClosedRole | OpenRole; -export const OpenessGuard: t.Type = t.union([ +export type Openness = ClosedRole | OpenRole; +export const OpenessGuard: t.Type = t.union([ ClosedRoleGuard, OpenRoleGuard, ]); @@ -129,7 +129,7 @@ export interface ClosedNFTWithMetadata { /** * @category Roles Configuration */ -export type Recipient = Openess; +export type Recipient = Openness; export const RecipientGuard: t.Type = t.union([ OpenRoleGuard, @@ -178,9 +178,8 @@ export const MintRolesTokensGuard: t.Type = t.record( * Defines how to configure Roles over Cardano at the creation of a Marlowe Contract. * @see * Smart Constructors are available to ease the configuration: - * - {@link @marlowe.io/runtime-rest-client!contract.mkUseMintedRoleTokens} - * - {@link @marlowe.io/runtime-rest-client!contract.mkMintOpenRoleToken} - * - {@link @marlowe.io/runtime-rest-client!contract.mkMintClosedRoleToken} + * - {@link @marlowe.io/runtime-rest-client!contract.useMintedRoles} + * - {@link @marlowe.io/runtime-rest-client!contract.mintRole} * @category Endpoint : Build Create Contract Tx * @category Roles Configuration */ @@ -209,42 +208,33 @@ export const RolesConfigurationGuard = t.union([ * @category Endpoint : Build Create Contract Tx * @category Roles Configuration */ -export const mkUseMintedRoleTokens = ( +export const useMintedRoles = ( policyId: PolicyId, openRoleNames?: RoleName[] ): RolesConfiguration => openRoleNames - ? { script: mkOpenRole, policyId: policyId, openRoleNames: openRoleNames } + ? { script: openRole, policyId: policyId, openRoleNames: openRoleNames } : (policyId as UsePolicyWithClosedRoleTokens); /** * Configure the minting of a Closed Role Token. - * @param address where to distribute the token that will be mint + * @param openness where to distribute the token (Either openly or closedly) * @param quantity Quantity of the Closed Role Token (by Default an NFT (==1)) * @param metadata Token Metadata of the Token * @category Endpoint : Build Create Contract Tx * @category Roles Configuration */ -export const mkMintClosedRoleToken: ( - address: AddressBech32, +export const mintRole = ( + openness: Openness, quantity?: TokenQuantity, metadata?: TokenMetadata -) => RoleTokenConfiguration = (address, quantity, metadata) => ({ - recipients: { [address]: quantity ? quantity : 1n }, - metadata: metadata, -}); - -/** - * Configure the minting of an Open Role Token. - * @param quantity Quantity of the Closed Role Token (by Default an NFT (==1)) - * @param metadata Token Metadata of the Token - * @category Endpoint : Build Create Contract Tx - * @category Roles Configuration - */ -export const mkMintOpenRoleToken: ( - quantity?: TokenQuantity, - metadata?: TokenMetadata -) => RoleTokenConfiguration = (quantity, metadata) => ({ - recipients: { [mkOpenRole]: quantity ? quantity : 1n }, - metadata: metadata, -}); +): RoleTokenConfiguration => + OpenRoleGuard.is(openness) + ? { + recipients: { [openRole]: quantity ? quantity : 1n }, + metadata: metadata, + } + : { + recipients: { [openness]: quantity ? quantity : 1n }, + metadata: metadata, + }; diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index 94803946..f7b44bbf 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -496,7 +496,7 @@ export interface ContractsAPI { * @description Dependency Injection for the Rest Client API * @hidden */ -export type RestDI = { rest: FPTSRestAPI }; +export type RestDI = { deprecatedRestAPI: FPTSRestAPI; restClient: RestClient }; /** * @hidden diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts index 30063dcb..03012dd8 100644 --- a/packages/runtime/lifecycle/src/api.ts +++ b/packages/runtime/lifecycle/src/api.ts @@ -6,13 +6,22 @@ import { PayoutAvailable, PayoutId, PayoutWithdrawn, + StakeAddressBech32, Tags, TxId, } from "@marlowe.io/runtime-core"; import { RestDI } from "@marlowe.io/runtime-rest-client"; -import { RolesConfiguration } from "@marlowe.io/runtime-rest-client/contract"; +import { + ContractOrSourceId, + RolesConfiguration, +} from "@marlowe.io/runtime-rest-client/contract"; import { ISO8601 } from "@marlowe.io/adapter/time"; -import { Contract, Environment, Input } from "@marlowe.io/language-core-v1"; +import { + Contract, + Environment, + Input, + RoleName, +} from "@marlowe.io/language-core-v1"; import { Next } from "@marlowe.io/language-core-v1/next"; export type RuntimeLifecycle = { @@ -29,10 +38,175 @@ export type RuntimeLifecycle = { export type ContractsDI = WalletDI & RestDI; export type CreateContractRequest = { - contract: Contract; - roles?: RolesConfiguration; + /** + * A Marlowe Contract or a Merkleized One (referred by its source Id) to create over Cardano + * @see Large/Deep Contracts Support (Contract Merkleization) and `@marlowe.io/language-core` + */ + contractOrSourceId: ContractOrSourceId; + + /** + * The Marlowe Runtime utilizes this Optional field to set a stake address + * where to send staking rewards for the Marlowe script outputs of this contract. + */ + stakeAddress?: StakeAddressBech32; + /** + * @experimental + * Thread Roles are a details of implementation within the runtime. It allows provide a custom name + * if the thread role name is conflicting with other role names used. + * @default + * - the Thread Role Name is "" by default. + */ + threadRoleName?: RoleName; + + /** + * Role Token Configuration for the contract passed in the `contractOrSourceId` field. + * + *

Prerequisite

+ *

+ * Participants ({@link @marlowe.io/language-core-v1!index.Party | Party}) in a Marlowe Contract can be expressed in 2 ways: + * + * 1. **By Adressses** : Addresses are directly defined within the Marlowe Contract and no configuration are necessary in that context. + * 2. **By Roles** : Defined by {@link @marlowe.io/language-core-v1!index.RoleName | RoleNames} within the Marlowe Contract, they have to match a Token Name when created in Cardano. This field `rolesConfiguration` is about configuring this use case + *

+ * + *

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 knowned at the creation and therfore we consider the participation as being closed. + * - **Openly** (Open Roles) : Whoever applies an input (IDeposit or IChoice) on the contract `contractOrSourceId` 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. + *

+ * + * @example + * + * ```ts + * ////////////// + * // #1 - Mint Role Tokens + * ////////////// + * const anAddressBech32 = "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja" + * const aMintingConfiguration = + * { "closed_Role_A_NFT" : mintRole(anAddressBech32) + * , "closed_Role_B_FT" : + * mintRole( + * anAddressBech32, + * 5, // Quantities + * { "name": "closed_Role_B_FT Marlowe Role Token", + "description": "These are metadata for closedRoleB", + * image": "ipfs://QmaQMH7ybS9KmdYQpa4FMtAhwJH5cNaacpg4fTwhfPvcwj", + * "mediaType": "image/png", + * "files": [ + * { + * "name": "icon-1000", + * "mediaType": "image/webp", + * "src": "ipfs://QmUbvavFxGSSEo3ipQf7rjrELDvXHDshWkHZSpV8CVdSE5" + * } + * ] + * }) + * , "open_Role_C" : mkMintOpenRoleToken() + * , "open_Role_D" : mkMintOpenRoleToken( + * 2, // Quantities + * { "name": "open_Role_D Marlowe Role Token", + "description": "These are metadata for closedRoleB", + * image": "ipfs://QmaQMH7ybS9KmdYQpa4FMtAhwJH5cNaacpg4fTwhfPvcwj", + * "mediaType": "image/png", + * "files": [ + * { + * "name": "icon-1000", + * "mediaType": "image/webp", + * "src": "ipfs://QmUbvavFxGSSEo3ipQf7rjrELDvXHDshWkHZSpV8CVdSE5" + * } + * ] + * }) + * } + * + * ////////////// + * // #2 Use Minted Roles Tokens + * const aUseMintedRoleTokensConfiguration = + * useMintedRoles( + * "e68f1cea19752d1292b4be71b7f5d2b3219a15859c028f7454f66cdf", + * ["role_A","role_C"] + * ) + * ``` + * + * @see + * - {@link @marlowe.io/runtime-rest-client!contract.useMintedRoles} + * - {@link @marlowe.io/runtime-rest-client!contract.mintRole} + * - Open Roles Runtime Implementation : https://github.com/input-output-hk/marlowe-cardano/blob/main/marlowe-runtime/doc/open-roles.md + */ + rolesConfiguration?: RolesConfiguration; + + /** + * Marlowe Tags are stored as Metadata within the Transaction Metadata under the top-level Marlowe Reserved Key (`1564`). + * Tags allows to Query created Marlowe Contracts via {@link @marlowe.io/runtime-rest-client!index.RestClient#getContracts | Get contracts } + * + *

Properties

+ * + * 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^64 - 1. + * - Each metadata value is tagged with its type. + * - Strings must be at most 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^64 - 1)` to `2^64 - 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 Contracts 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 (mininmumLovelaceUTxODeposit)" that adjust + * the value (in ADA) of each UTxO has been added. + * + * The more the UTxOs entries are big in size, the more the value of minimum + * of ADAs needs to be contained.

+ * @see + * https://docs.cardano.org/native-tokens/minimum-ada-value-requirement + */ mininmumLovelaceUTxODeposit?: number; }; diff --git a/packages/runtime/lifecycle/src/browser/index.ts b/packages/runtime/lifecycle/src/browser/index.ts index 81a60ebd..6619bb90 100644 --- a/packages/runtime/lifecycle/src/browser/index.ts +++ b/packages/runtime/lifecycle/src/browser/index.ts @@ -4,7 +4,10 @@ import { } from "@marlowe.io/wallet/browser"; import * as Generic from "../generic/runtime.js"; -import { mkFPTSRestClient } from "@marlowe.io/runtime-rest-client"; +import { + mkFPTSRestClient, + mkRestClient, +} from "@marlowe.io/runtime-rest-client"; /** * Options for creating a RuntimeLifecycle instance using the browser wallet. @@ -31,6 +34,7 @@ export async function mkRuntimeLifecycle({ walletName, }: BrowserRuntimeLifecycleOptions) { const wallet = await mkBrowserWallet(walletName); - const restClient = mkFPTSRestClient(runtimeURL); - return Generic.mkRuntimeLifecycle(restClient, wallet); + const deprecatedRestAPI = mkFPTSRestClient(runtimeURL); + const restClient = mkRestClient(runtimeURL); + return Generic.mkRuntimeLifecycle(deprecatedRestAPI, restClient, wallet); } diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts index f54bd1c7..deb4063f 100644 --- a/packages/runtime/lifecycle/src/generic/contracts.ts +++ b/packages/runtime/lifecycle/src/generic/contracts.ts @@ -19,23 +19,26 @@ import { HexTransactionWitnessSet, unAddressBech32, unPolicyId, + transactionWitnessSetTextEnvelope, } from "@marlowe.io/runtime-core"; -import { FPTSRestAPI } from "@marlowe.io/runtime-rest-client"; +import { FPTSRestAPI, RestClient } from "@marlowe.io/runtime-rest-client"; import { DecodingError } from "@marlowe.io/adapter/codec"; import { Next, noNext } from "@marlowe.io/language-core-v1/next"; import { isNone, none, Option } from "fp-ts/lib/Option.js"; import { + BuildCreateContractTxResponse, ContractsRange, TransactionTextEnvelope, } from "@marlowe.io/runtime-rest-client/contract"; export function mkContractLifecycle( wallet: WalletAPI, - rest: FPTSRestAPI + deprecatedRestAPI: FPTSRestAPI, + restClient: RestClient ): ContractsAPI { - const di = { wallet, rest }; + const di = { wallet, deprecatedRestAPI, restClient }; return { createContract: submitCreateTx(di), applyInputs: submitApplyInputsTx(di), @@ -45,31 +48,33 @@ export function mkContractLifecycle( } const submitCreateTx = - ({ wallet, rest }: ContractsDI) => + ({ wallet, restClient }: ContractsDI) => ( createContractRequest: CreateContractRequest ): Promise<[ContractId, TxId]> => { return unsafeTaskEither( - submitCreateTxFpTs(rest)(wallet)(createContractRequest) + submitCreateTxFpTs(restClient)(wallet)(createContractRequest) ); }; const submitApplyInputsTx = - ({ wallet, rest }: ContractsDI) => + ({ wallet, deprecatedRestAPI }: ContractsDI) => async ( contractId: ContractId, applyInputsRequest: ApplyInputsRequest ): Promise => { return unsafeTaskEither( - submitApplyInputsTxFpTs(rest)(wallet)(contractId)(applyInputsRequest) + submitApplyInputsTxFpTs(deprecatedRestAPI)(wallet)(contractId)( + applyInputsRequest + ) ); }; const getApplicableInputs = - ({ wallet, rest }: ContractsDI) => + ({ wallet, deprecatedRestAPI }: ContractsDI) => async (contractId: ContractId, environement: Environment): Promise => { const contractDetails = await unsafeTaskEither( - rest.contracts.contract.get(contractId) + deprecatedRestAPI.contracts.contract.get(contractId) ); if (isNone(contractDetails.currentContract)) { return noNext; @@ -78,13 +83,15 @@ const getApplicableInputs = contractDetails.roleTokenMintingPolicyId ); return await unsafeTaskEither( - rest.contracts.contract.next(contractId)(environement)(parties) + deprecatedRestAPI.contracts.contract.next(contractId)(environement)( + parties + ) ); } }; const getContractIds = - ({ rest, wallet }: ContractsDI) => + ({ deprecatedRestAPI, wallet }: ContractsDI) => async (): Promise => { const partyAddresses = [ await wallet.getChangeAddress(), @@ -95,7 +102,9 @@ const getContractIds = range: Option, acc: ContractId[] ): Promise => { - const result = await rest.contracts.getHeadersByRange(range)(kwargs)(); + const result = await deprecatedRestAPI.contracts.getHeadersByRange(range)( + kwargs + )(); if (result._tag === "Left") throw result.left; const response = result.right; const contractIds = [ @@ -130,7 +139,7 @@ const getParties: ( }; export const submitCreateTxFpTs: ( - client: FPTSRestAPI + client: RestClient ) => ( wallet: WalletAPI ) => ( @@ -140,40 +149,48 @@ export const submitCreateTxFpTs: ( pipe( tryCatchDefault(() => getAddressesAndCollaterals(wallet)), TE.chain((addressesAndCollaterals) => - client.contracts.post( - { - contract: createContractRequest.contract, + tryCatchDefault(() => + client.buildCreateContractTx({ version: "v1", - roles: createContractRequest.roles, - tags: createContractRequest.tags ? createContractRequest.tags : {}, - metadata: createContractRequest.metadata - ? createContractRequest.metadata - : {}, - ...(createContractRequest.mininmumLovelaceUTxODeposit && { - mininmumLovelaceUTxODeposit: - createContractRequest.mininmumLovelaceUTxODeposit, - }), - }, - addressesAndCollaterals + + changeAddress: addressesAndCollaterals.changeAddress, + usedAddresses: addressesAndCollaterals.usedAddresses, + collateralUTxOs: addressesAndCollaterals.collateralUTxOs, + stakeAddress: createContractRequest.stakeAddress, + + contractOrSourceId: createContractRequest.contractOrSourceId, + threadRoleName: createContractRequest.threadRoleName, + rolesConfiguration: createContractRequest.rolesConfiguration, + + tags: createContractRequest.tags, + metadata: createContractRequest.metadata, + mininmumLovelaceUTxODeposit: + createContractRequest.mininmumLovelaceUTxODeposit, + }) ) ), - TE.chainW((contractTextEnvelope) => - pipe( - tryCatchDefault(() => wallet.signTx(contractTextEnvelope.tx.cborHex)), - TE.chain((hexTransactionWitnessSet) => - client.contracts.contract.put( - contractTextEnvelope.contractId, - hexTransactionWitnessSet - ) - ), - TE.map(() => contractTextEnvelope.contractId) - ) + TE.chainW( + (buildCreateContractTxResponse: BuildCreateContractTxResponse) => + pipe( + tryCatchDefault(() => + wallet.signTx(buildCreateContractTxResponse.tx.cborHex) + ), + TE.chain((hexTransactionWitnessSet) => + tryCatchDefault(() => + client.submitContract( + buildCreateContractTxResponse.contractId, + transactionWitnessSetTextEnvelope(hexTransactionWitnessSet) + ) + ) + ), + TE.map(() => buildCreateContractTxResponse.contractId) + ) ), TE.map((contractId) => [contractId, contractIdToTxId(contractId)]) ); export const createContractFpTs: ( - client: FPTSRestAPI + client: RestClient ) => ( wallet: WalletAPI ) => ( diff --git a/packages/runtime/lifecycle/src/generic/payouts.ts b/packages/runtime/lifecycle/src/generic/payouts.ts index 7dff0800..5451bcb2 100644 --- a/packages/runtime/lifecycle/src/generic/payouts.ts +++ b/packages/runtime/lifecycle/src/generic/payouts.ts @@ -17,7 +17,7 @@ import { withdrawalIdToTxId, } from "@marlowe.io/runtime-core"; -import { FPTSRestAPI } from "@marlowe.io/runtime-rest-client"; +import { FPTSRestAPI, RestClient } from "@marlowe.io/runtime-rest-client"; import * as RestPayout from "@marlowe.io/runtime-rest-client/payout"; @@ -26,9 +26,10 @@ import { stringify } from "json-bigint"; export function mkPayoutLifecycle( wallet: WalletAPI, - rest: FPTSRestAPI + deprecatedRestAPI: FPTSRestAPI, + restClient: RestClient ): PayoutsAPI { - const di = { wallet, rest }; + const di = { wallet, deprecatedRestAPI, restClient }; return { available: fetchAvailablePayouts(di), withdraw: withdrawPayouts(di), @@ -37,22 +38,28 @@ export function mkPayoutLifecycle( } const fetchAvailablePayouts = - ({ wallet, rest }: PayoutsDI) => + ({ wallet, deprecatedRestAPI }: PayoutsDI) => (filters?: Filters): Promise => { return unsafeTaskEither( - fetchAvailablePayoutsFpTs(rest)(wallet)(O.fromNullable(filters)) + fetchAvailablePayoutsFpTs(deprecatedRestAPI)(wallet)( + O.fromNullable(filters) + ) ); }; const withdrawPayouts = - ({ wallet, rest }: PayoutsDI) => + ({ wallet, deprecatedRestAPI }: PayoutsDI) => (payoutIds: PayoutId[]): Promise => { - return unsafeTaskEither(withdrawPayoutsFpTs(rest)(wallet)(payoutIds)); + return unsafeTaskEither( + withdrawPayoutsFpTs(deprecatedRestAPI)(wallet)(payoutIds) + ); }; const fetchWithdrawnPayouts = - ({ wallet, rest }: PayoutsDI) => + ({ wallet, deprecatedRestAPI }: PayoutsDI) => (filters?: Filters): Promise => { return unsafeTaskEither( - fetchWithdrawnPayoutsFpTs(rest)(wallet)(O.fromNullable(filters)) + fetchWithdrawnPayoutsFpTs(deprecatedRestAPI)(wallet)( + O.fromNullable(filters) + ) ); }; diff --git a/packages/runtime/lifecycle/src/generic/runtime.ts b/packages/runtime/lifecycle/src/generic/runtime.ts index 6aaf8095..990e3b94 100644 --- a/packages/runtime/lifecycle/src/generic/runtime.ts +++ b/packages/runtime/lifecycle/src/generic/runtime.ts @@ -1,18 +1,19 @@ import { RuntimeLifecycle } from "../api.js"; import { WalletAPI } from "@marlowe.io/wallet/api"; -import { FPTSRestAPI } from "@marlowe.io/runtime-rest-client"; +import { FPTSRestAPI, RestClient } from "@marlowe.io/runtime-rest-client"; import { mkPayoutLifecycle } from "./payouts.js"; import { mkContractLifecycle } from "./contracts.js"; export function mkRuntimeLifecycle( - restAPI: FPTSRestAPI, + deprecatedRestAPI: FPTSRestAPI, + restClient: RestClient, wallet: WalletAPI ): RuntimeLifecycle { return { wallet: wallet, - contracts: mkContractLifecycle(wallet, restAPI), - payouts: mkPayoutLifecycle(wallet, restAPI), + contracts: mkContractLifecycle(wallet, deprecatedRestAPI, restClient), + payouts: mkPayoutLifecycle(wallet, deprecatedRestAPI, restClient), }; } diff --git a/packages/runtime/lifecycle/src/index.ts b/packages/runtime/lifecycle/src/index.ts index ca3a30e9..b96249cf 100644 --- a/packages/runtime/lifecycle/src/index.ts +++ b/packages/runtime/lifecycle/src/index.ts @@ -17,7 +17,7 @@ import { WalletAPI } from "@marlowe.io/wallet"; import * as Generic from "./generic/runtime.js"; -import { mkFPTSRestClient } from "@marlowe.io/runtime-rest-client"; +import { mkFPTSRestClient, mkRestClient } from "@marlowe.io/runtime-rest-client"; export * as Browser from "./browser/index.js"; @@ -42,6 +42,7 @@ export function mkRuntimeLifecycle({ runtimeURL, wallet, }: RuntimeLifecycleOptions) { - const restClient = mkFPTSRestClient(runtimeURL); - return Generic.mkRuntimeLifecycle(restClient, wallet); + const deprecatedRestAPI = mkFPTSRestClient(runtimeURL); + const restClient = mkRestClient(runtimeURL); + return Generic.mkRuntimeLifecycle(deprecatedRestAPI,restClient, wallet); } diff --git a/packages/runtime/lifecycle/src/nodejs/index.ts b/packages/runtime/lifecycle/src/nodejs/index.ts new file mode 100644 index 00000000..f5614c71 --- /dev/null +++ b/packages/runtime/lifecycle/src/nodejs/index.ts @@ -0,0 +1,25 @@ +import { + mkFPTSRestClient, + mkRestClient, +} from "@marlowe.io/runtime-rest-client"; +import * as S from "@marlowe.io/wallet/nodejs"; +import * as Generic from "../generic/runtime.js"; + +export async function mkRuntimeLifecycle({ + runtimeURL, + context, + privateKeyBech32, +}: { + runtimeURL: string; + context: S.Context; + privateKeyBech32: string; +}) { + const wallet = await S.SingleAddressWallet.Initialise( + context, + privateKeyBech32 + ); + + const deprecatedRestAPI = mkFPTSRestClient(runtimeURL); + const restClient = mkRestClient(runtimeURL); + return Generic.mkRuntimeLifecycle(deprecatedRestAPI, restClient, wallet); +} 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 8fc83cef..7ab37112 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 @@ -13,9 +13,18 @@ import { } from "../context.js"; import { provisionAnAdaAndTokenProvider } from "../provisionning.js"; import console from "console"; -import { runtimeTokenToMarloweTokenValue } from "@marlowe.io/runtime-core"; +import { + runtimeTokenToMarloweTokenValue, + unAddressBech32, +} 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"; +import { + AddressBech32, + AddressBech32Guard, +} from "@marlowe.io/runtime-rest-client/contract/rolesConfigurations.js"; +import { unsafeEither } from "@marlowe.io/adapter/fp-ts"; global.console = console; @@ -57,10 +66,14 @@ describe("swap", () => { const [contractId, txIdContractCreated] = await runtime( adaProvider ).contracts.createContract({ - contract: swapContract, - roles: { - [swapRequest.provider.roleName]: adaProvider.address, - [swapRequest.swapper.roleName]: tokenProvider.address, + contractOrSourceId: swapContract, + rolesConfiguration: { + [swapRequest.provider.roleName]: mintRole( + adaProvider.address as unknown as AddressBech32 + ), + [swapRequest.swapper.roleName]: mintRole( + tokenProvider.address as unknown as AddressBech32 + ), }, }); await runtime(adaProvider).wallet.waitConfirmation(txIdContractCreated); diff --git a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts index 644bfb7f..f86a102f 100644 --- a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts +++ b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts @@ -33,7 +33,7 @@ describe("Runtime Contract Lifecycle ", () => { getBankPrivateKey() ); const [contractId, txIdContractCreated] = - await runtime.contracts.createContract({ contract: close }); + await runtime.contracts.createContract({ contractOrSourceId: close }); await runtime.wallet.waitConfirmation(txIdContractCreated); console.log("contractID created", contractId); }, @@ -51,7 +51,7 @@ describe("Runtime Contract Lifecycle ", () => { const notifyTimeout = pipe(addDays(Date.now(), 1), datetoTimeout); const [contractId, txIdContractCreated] = await runtime.contracts.createContract({ - contract: oneNotifyTrue(notifyTimeout), + contractOrSourceId: oneNotifyTrue(notifyTimeout), }); await runtime.wallet.waitConfirmation(txIdContractCreated); diff --git a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts index c9c1fcaf..9dfdc38c 100644 --- a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts +++ b/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts @@ -16,6 +16,8 @@ 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"; +import { AddressBech32 } from "@marlowe.io/runtime-rest-client/contract/rolesConfigurations.js"; global.console = console; @@ -50,10 +52,14 @@ describe("Payouts", () => { const [contractId, txCreatedContract] = await runtime( adaProvider ).contracts.createContract({ - contract: swapContract, - roles: { - [swapRequest.provider.roleName]: adaProvider.address, - [swapRequest.swapper.roleName]: tokenProvider.address, + contractOrSourceId: swapContract, + rolesConfiguration: { + [swapRequest.provider.roleName]: mintRole( + adaProvider.address as unknown as AddressBech32 + ), + [swapRequest.swapper.roleName]: mintRole( + tokenProvider.address as unknown as AddressBech32 + ), }, }); await runtime(adaProvider).wallet.waitConfirmation(txCreatedContract); From c2cb298fea3391e5951e0b7015b53f41a0117020 Mon Sep 17 00:00:00 2001 From: nhenin Date: Wed, 6 Dec 2023 14:08:20 +0100 Subject: [PATCH 08/13] [wallet] fixed case sensitive issue when searhing for extensions --- packages/wallet/src/browser/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wallet/src/browser/index.ts b/packages/wallet/src/browser/index.ts index 0e000663..95ffd293 100644 --- a/packages/wallet/src/browser/index.ts +++ b/packages/wallet/src/browser/index.ts @@ -78,8 +78,8 @@ export async function mkBrowserWallet( ): Promise { if ( getInstalledWalletExtensions() - .map((extension) => extension.name) - .includes(walletName) + .map((extension) => extension.name.toLowerCase()) + .includes(walletName.toLowerCase()) ) { const extension = await window.cardano[walletName.toLowerCase()].enable(); const di = { extension }; From fa1ed6579e35ecc74a309ca2b43432c3b5f5f368 Mon Sep 17 00:00:00 2001 From: nhenin Date: Thu, 7 Dec 2023 11:26:05 +0100 Subject: [PATCH 09/13] PR feedback --- packages/adapter/src/codec.ts | 2 +- .../rest/src/contract/endpoints/collection.ts | 75 ++++++++++++++----- .../client/rest/src/contract/guards.ts | 2 +- .../rest/src/contract/rolesConfigurations.ts | 7 +- packages/runtime/client/rest/src/index.ts | 12 +-- packages/runtime/lifecycle/src/api.ts | 43 ++++++----- .../lifecycle/src/generic/contracts.ts | 6 +- .../test/examples/swap.ada.token.e2e.spec.ts | 5 +- .../test/generic/payouts.e2e.spec.ts | 2 +- 9 files changed, 96 insertions(+), 58 deletions(-) diff --git a/packages/adapter/src/codec.ts b/packages/adapter/src/codec.ts index 0ae7ec29..bca77474 100644 --- a/packages/adapter/src/codec.ts +++ b/packages/adapter/src/codec.ts @@ -21,7 +21,7 @@ export type DecodingError = string[]; export class UnexpectedRuntimeResponse extends Error { public type = "UnexpectedRuntimeResponse" as const; constructor(message: string) { - super(); + super(message); } } diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index b03bbb34..4ba3cc6a 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -199,7 +199,7 @@ export const GetContractsResponseGuard = assertGuardEqual( ); /** - * Either a Non Merkleized Marlowe Contract or a Merkleized One + * Either a non-merkleized Marlowe Contract or a merkleized One * @category Endpoint : Build Create Contract Tx */ export type ContractOrSourceId = Contract | SourceId; @@ -216,11 +216,46 @@ export const ContractOrSourceIdGuard: t.Type = t.union([ /** * Request options for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint * @category Endpoint : Build Create Contract Tx + * @example + * - Minimal Simple Contract Close + * ```json + * { "changeAddress" : "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja", + * "contractOrSourceId" : "close", + * "tags" : {"ts-sdk.documentation.example" : {"infoA" : 5} }, + * "version" : "v1" + * } + * ``` + * - Simple Contract Close with Optional Fields + * ```json + * { "changeAddress" : "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja", + * "usedAddresses": ["addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja"], + * "collateralUTxOs": [], + * "contractOrSourceId" : "close", + * "tags" : {"ts-sdk.documentation.example" : {"infoA" : 5} }, + * "minimumLovelaceUTxODeposit" : 3000000, + * "threadRoleName" : "ThreadRoleToken", + * "version" : "v1" + * } + * ``` + * - Swap Contract Copy/Pasted from Playground with Role Token Configuration (NFT Open Role for Both) + * ```json + * { "changeAddress" : "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja", + * "usedAddresses": ["addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja"], + * "collateralUTxOs": [], + * "contractOrSourceId" : {"when":[{"then":{"when":[{"then":{"token":{"token_name":"","currency_symbol":""},"to":{"party":{"role_token":"Dollar provider"}},"then":{"token":{"token_name":"dollar","currency_symbol":"85bb65"},"to":{"party":{"role_token":"Ada provider"}},"then":"close","pay":0,"from_account":{"role_token":"Dollar provider"}},"pay":{"times":0,"multiply":1000000},"from_account":{"role_token":"Ada provider"}},"case":{"party":{"role_token":"Dollar provider"},"of_token":{"token_name":"dollar","currency_symbol":"85bb65"},"into_account":{"role_token":"Dollar provider"},"deposits":0}}],"timeout_continuation":"close","timeout":1701773934770},"case":{"party":{"role_token":"Ada provider"},"of_token":{"token_name":"","currency_symbol":""},"into_account":{"role_token":"Ada provider"},"deposits":{"times":0,"multiply":1000000}}}],"timeout_continuation":"close","timeout":1701772134770}, + * "tags" : {"ts-sdk.documentation.example" : {"infoA" : 5} }, + * "roles" : {"Ada provider" : {"recipients": {"OpenRole" : 1} } + * ,"Dollar provider" : {"recipients": {"OpenRole" : 1} } }, + * "minimumLovelaceUTxODeposit" : 3000000, + * "threadRoleName" : "ThreadRoleToken", + * "version" : "v1" + * } + * ``` */ export interface BuildCreateContractTxRequest { /** - * The Marlowe Runtime utilizes this Optional field to set a stake address - * where to send staking rewards for the Marlowe script outputs of this contract. + * 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; /** @@ -300,14 +335,14 @@ export interface BuildCreateContractTxRequest { *

Properties

*

* Metadata can be expressed as a JSON object with some restrictions: - * - All top-level keys must be integers between 0 and 2^64 - 1. + * - 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 bytes when UTF-8 is encoded. + * - 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^64 - 1)` to `2^64 - 1` + * - Integers in the range `-(2^63 - 1)` to `2^63 - 1` * - Strings (UTF-8 encoded) * - Bytestrings * - And two compound types: @@ -324,37 +359,36 @@ export interface BuildCreateContractTxRequest { * is computed automatically within the Runtime, so this parameter is only necessary if you need some custom adjustment. * *

Justification

- *

Creating a Marlowe Contracts over Cardano is about creating UTxO entries on the Ledger. + *

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 (mininmumLovelaceUTxODeposit)" that adjust + * a constraint called "Minimum ada value requirement (minimumLovelaceUTxODeposit)" that adjust * the value (in ADA) of each UTxO has been added. * - * The more the UTxOs entries are big in size, the more the value of minimum - * of ADAs needs to be contained.

+ * 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 */ - mininmumLovelaceUTxODeposit?: number; + minimumLovelaceUTxODeposit?: number; /** * @experimental - * Thread Roles are a details of implementation within the runtime. It allows provide a custom name - * if the thread role name is conflicting with other role names used. + * 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. */ threadRoleName?: RoleName; /** - * Role Token Configuration for the contract passed in the `contractOrSourceId` field. + * Role Token Configuration for the new contract passed in the `contractOrSourceId` field. * - *

Prerequisite

+ *

Participants

*

* Participants ({@link @marlowe.io/language-core-v1!index.Party | Party}) in a Marlowe Contract can be expressed in 2 ways: * - * 1. **By Adressses** : Addresses are directly defined within the Marlowe Contract and no configuration are necessary in that context. - * 2. **By Roles** : Defined by {@link @marlowe.io/language-core-v1!index.RoleName | RoleNames} within the Marlowe Contract, they have to match a Token Name when created in Cardano. This field `rolesConfiguration` is about configuring this use case + * 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

@@ -364,7 +398,7 @@ export interface BuildCreateContractTxRequest { * - **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 knowned at the creation and therfore we consider the participation as being closed. + * - **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 `contractOrSourceId` 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. @@ -434,7 +468,7 @@ export interface BuildCreateContractTxRequest { * - {@link @marlowe.io/runtime-rest-client!contract.mintRole} * - Open Roles Runtime Implementation : https://github.com/input-output-hk/marlowe-cardano/blob/main/marlowe-runtime/doc/open-roles.md */ - rolesConfiguration?: RolesConfiguration; + roles?: RolesConfiguration; /** * The Marlowe validator version to use. @@ -457,12 +491,13 @@ export type PostContractsRequest = t.TypeOf; */ export const PostContractsRequest = t.intersection([ t.type({ - contract: ContractOrSourceIdGuard, version: MarloweVersion, + contract: ContractOrSourceIdGuard, tags: TagsGuard, metadata: Metadata, }), t.partial({ roles: RolesConfigurationGuard }), + t.partial({ threadTokenName: G.RoleName }), t.partial({ minUTxODeposit: t.number }), ]); diff --git a/packages/runtime/client/rest/src/contract/guards.ts b/packages/runtime/client/rest/src/contract/guards.ts index 93c1faa0..420d6a87 100644 --- a/packages/runtime/client/rest/src/contract/guards.ts +++ b/packages/runtime/client/rest/src/contract/guards.ts @@ -16,7 +16,7 @@ export { ClosedRoleGuard as ClosedRole, OpenRoleGuard as OpenRole, - OpenessGuard as Openess, + OpennessGuard as Openess, UsePolicyWithClosedRoleTokensGuard as UsePolicyWithClosedRoleTokens, UsePolicyWithOpenRoleTokensGuard as UsePolicyWithOpenRoleTokens, TokenMetadataFileGuard as TokenMetadataFile, diff --git a/packages/runtime/client/rest/src/contract/rolesConfigurations.ts b/packages/runtime/client/rest/src/contract/rolesConfigurations.ts index b87ff721..b8ebb005 100644 --- a/packages/runtime/client/rest/src/contract/rolesConfigurations.ts +++ b/packages/runtime/client/rest/src/contract/rolesConfigurations.ts @@ -50,7 +50,7 @@ export const openRole = "OpenRole"; * @category Roles Configuration */ export type Openness = ClosedRole | OpenRole; -export const OpenessGuard: t.Type = t.union([ +export const OpennessGuard: t.Type = t.union([ ClosedRoleGuard, OpenRoleGuard, ]); @@ -94,11 +94,10 @@ export const TokenMetadataFileGuard: t.Type = t.type({ }); /** + * Token Metadata (CIP-25) * @category Roles Configuration - * TODO : Which CIP are we following here ? * @see - * - https://developers.cardano.org/docs/native-tokens/minting-nfts/ - * - https://docs.nmkr.io/nmkr-studio/token/metadata/metadata-standard-for-fungible-tokens + * - https://github.com/cardano-foundation/CIPs/blob/master/CIP-0025/README.md */ export interface TokenMetadata { name?: string; diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index f7b44bbf..44c2d495 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -57,7 +57,6 @@ import { CreateContractSourcesResponse } from "./contract/endpoints/sources.js"; * * **WARNING**: Not all endpoints are implemented yet. */ -// DISCUSSION: @N.H: Should we rename this to RestClient? export interface RestClient { /** * Gets a paginated list of contracts {@link contract.ContractHeader } @@ -254,11 +253,14 @@ export function mkRestClient(baseURL: string): RestClient { version: request.version, metadata: request.metadata ?? {}, tags: request.tags ?? {}, - ...(request.mininmumLovelaceUTxODeposit && { - minUTxODeposit: request.mininmumLovelaceUTxODeposit, + ...(request.minimumLovelaceUTxODeposit && { + minUTxODeposit: request.minimumLovelaceUTxODeposit, }), - ...(request.rolesConfiguration && { - roles: request.rolesConfiguration, + ...(request.roles && { + roles: request.roles, + }), + ...(request.threadRoleName && { + threadTokenName: request.threadRoleName, }), }; const addressesAndCollaterals = { diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts index 03012dd8..8adaf8ca 100644 --- a/packages/runtime/lifecycle/src/api.ts +++ b/packages/runtime/lifecycle/src/api.ts @@ -39,34 +39,37 @@ export type ContractsDI = WalletDI & RestDI; export type CreateContractRequest = { /** - * A Marlowe Contract or a Merkleized One (referred by its source Id) to create over Cardano + * A Marlowe Contract or a merkleized One (referred by its source Id) to create over Cardano * @see Large/Deep Contracts Support (Contract Merkleization) and `@marlowe.io/language-core` */ contractOrSourceId: ContractOrSourceId; /** - * The Marlowe Runtime utilizes this Optional field to set a stake address - * where to send staking rewards for the Marlowe script outputs of this contract. + * 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 - * Thread Roles are a details of implementation within the runtime. It allows provide a custom name - * if the thread role name is conflicting with other role names used. + * 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 contract passed in the `contractOrSourceId` field. + * Role Token Configuration for the new contract passed in the `contractOrSourceId` field. * - *

Prerequisite

+ *

Participants

*

* Participants ({@link @marlowe.io/language-core-v1!index.Party | Party}) in a Marlowe Contract can be expressed in 2 ways: - * - * 1. **By Adressses** : Addresses are directly defined within the Marlowe Contract and no configuration are necessary in that context. - * 2. **By Roles** : Defined by {@link @marlowe.io/language-core-v1!index.RoleName | RoleNames} within the Marlowe Contract, they have to match a Token Name when created in Cardano. This field `rolesConfiguration` is about configuring this use case + * + * 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

@@ -76,7 +79,7 @@ export type CreateContractRequest = { * - **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 knowned at the creation and therfore we consider the participation as being closed. + * - **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 `contractOrSourceId` 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. @@ -145,7 +148,7 @@ export type CreateContractRequest = { * - {@link @marlowe.io/runtime-rest-client!contract.mintRole} * - Open Roles Runtime Implementation : https://github.com/input-output-hk/marlowe-cardano/blob/main/marlowe-runtime/doc/open-roles.md */ - rolesConfiguration?: RolesConfiguration; + roles?: RolesConfiguration; /** * Marlowe Tags are stored as Metadata within the Transaction Metadata under the top-level Marlowe Reserved Key (`1564`). @@ -171,14 +174,14 @@ export type CreateContractRequest = { *

Properties

*

* Metadata can be expressed as a JSON object with some restrictions: - * - All top-level keys must be integers between 0 and 2^64 - 1. + * - 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 bytes when UTF-8 is encoded. + * - 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^64 - 1)` to `2^64 - 1` + * - Integers in the range `-(2^63 - 1)` to `2^63 - 1` * - Strings (UTF-8 encoded) * - Bytestrings * - And two compound types: @@ -196,18 +199,18 @@ export type CreateContractRequest = { * is computed automatically within the Runtime, so this parameter is only necessary if you need some custom adjustment. * *

Justification

- *

Creating a Marlowe Contracts over Cardano is about creating UTxO entries on the Ledger. + *

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 (mininmumLovelaceUTxODeposit)" that adjust + * a constraint called "Minimum ada value requirement (minimumLovelaceUTxODeposit)" that adjust * the value (in ADA) of each UTxO has been added. * - * The more the UTxOs entries are big in size, the more the value of minimum - * of ADAs needs to be contained.

+ * 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 */ - mininmumLovelaceUTxODeposit?: number; + minimumLovelaceUTxODeposit?: number; }; export type ApplyInputsRequest = { diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts index deb4063f..5b8e9ab8 100644 --- a/packages/runtime/lifecycle/src/generic/contracts.ts +++ b/packages/runtime/lifecycle/src/generic/contracts.ts @@ -160,12 +160,12 @@ export const submitCreateTxFpTs: ( contractOrSourceId: createContractRequest.contractOrSourceId, threadRoleName: createContractRequest.threadRoleName, - rolesConfiguration: createContractRequest.rolesConfiguration, + roles: createContractRequest.roles, tags: createContractRequest.tags, metadata: createContractRequest.metadata, - mininmumLovelaceUTxODeposit: - createContractRequest.mininmumLovelaceUTxODeposit, + minimumLovelaceUTxODeposit: + createContractRequest.minimumLovelaceUTxODeposit, }) ) ), 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 7ab37112..9b10e663 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 @@ -19,12 +19,11 @@ import { } 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"; +import { mintRole, openRole } from "@marlowe.io/runtime-rest-client/contract"; import { AddressBech32, AddressBech32Guard, } from "@marlowe.io/runtime-rest-client/contract/rolesConfigurations.js"; -import { unsafeEither } from "@marlowe.io/adapter/fp-ts"; global.console = console; @@ -67,7 +66,7 @@ describe("swap", () => { adaProvider ).contracts.createContract({ contractOrSourceId: swapContract, - rolesConfiguration: { + roles: { [swapRequest.provider.roleName]: mintRole( adaProvider.address as unknown as AddressBech32 ), diff --git a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts index 9dfdc38c..a0e14ec6 100644 --- a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts +++ b/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts @@ -53,7 +53,7 @@ describe("Payouts", () => { adaProvider ).contracts.createContract({ contractOrSourceId: swapContract, - rolesConfiguration: { + roles: { [swapRequest.provider.roleName]: mintRole( adaProvider.address as unknown as AddressBech32 ), From 523f9fd64674131f76d18d057ec6531a372ba7ef Mon Sep 17 00:00:00 2001 From: nhenin Date: Thu, 7 Dec 2023 16:43:51 +0100 Subject: [PATCH 10/13] [runtime.lifecycle] renamed contractOrSourceId to contract in ContractAPI --- examples/get-my-contract-ids-flow/index.html | 2 +- examples/run-lite/index.html | 2 +- examples/survey-workshop/participant/index.js | 2 +- examples/vesting-flow/index.html | 2 +- packages/runtime/lifecycle/src/api.ts | 14 +++++--------- .../runtime/lifecycle/src/generic/contracts.ts | 2 +- .../test/examples/swap.ada.token.e2e.spec.ts | 2 +- .../lifecycle/test/generic/contracts.e2e.spec.ts | 4 ++-- .../lifecycle/test/generic/payouts.e2e.spec.ts | 2 +- 9 files changed, 14 insertions(+), 18 deletions(-) diff --git a/examples/get-my-contract-ids-flow/index.html b/examples/get-my-contract-ids-flow/index.html index e18a8109..3cc18051 100644 --- a/examples/get-my-contract-ids-flow/index.html +++ b/examples/get-my-contract-ids-flow/index.html @@ -41,7 +41,7 @@ window.createContract = async () => { const address = await window.runtimeLifeCycle.wallet.getChangeAddress(); return runtimeLifeCycle.contracts.createContract({ - contractOrSourceId: mkContract(address, Date.now() + 1000 * 60 * 10), + contract: mkContract(address, Date.now() + 1000 * 60 * 10), }); }; diff --git a/examples/run-lite/index.html b/examples/run-lite/index.html index 1e64c0ac..8e146a0d 100644 --- a/examples/run-lite/index.html +++ b/examples/run-lite/index.html @@ -109,7 +109,7 @@

Console

}); const contractId = await runtime.contracts.createContract({ - contractOrSourceId: contract, + contract: contract, }); log("Contract created with id: " + contractId); setContractIdIndicator(contractId); diff --git a/examples/survey-workshop/participant/index.js b/examples/survey-workshop/participant/index.js index d081c565..b63a2813 100644 --- a/examples/survey-workshop/participant/index.js +++ b/examples/survey-workshop/participant/index.js @@ -124,7 +124,7 @@ async function createContract( const lifecycle = await H.getLifecycle(); const [contractId] = await lifecycle.contracts.createContract({ - contractOrSourceId: contract, + contract: contract, tags: { MarloweSurvey: "test 1" }, }); diff --git a/examples/vesting-flow/index.html b/examples/vesting-flow/index.html index 03498256..b014da5d 100644 --- a/examples/vesting-flow/index.html +++ b/examples/vesting-flow/index.html @@ -116,7 +116,7 @@

Console

const [contractId, txIdCreated] = await runtimeLifeCycle.contracts.createContract({ - contractOrSourceId: vestingContract, + contract: vestingContract, tags: { [dappId]: { scheme: request.scheme } }, }); log( diff --git a/packages/runtime/lifecycle/src/api.ts b/packages/runtime/lifecycle/src/api.ts index 8adaf8ca..6105fc1a 100644 --- a/packages/runtime/lifecycle/src/api.ts +++ b/packages/runtime/lifecycle/src/api.ts @@ -11,10 +11,7 @@ import { TxId, } from "@marlowe.io/runtime-core"; import { RestDI } from "@marlowe.io/runtime-rest-client"; -import { - ContractOrSourceId, - RolesConfiguration, -} from "@marlowe.io/runtime-rest-client/contract"; +import { RolesConfiguration } from "@marlowe.io/runtime-rest-client/contract"; import { ISO8601 } from "@marlowe.io/adapter/time"; import { Contract, @@ -39,10 +36,9 @@ export type ContractsDI = WalletDI & RestDI; export type CreateContractRequest = { /** - * A Marlowe Contract or a merkleized One (referred by its source Id) to create over Cardano - * @see Large/Deep Contracts Support (Contract Merkleization) and `@marlowe.io/language-core` + * A Marlowe Contract to create over Cardano */ - contractOrSourceId: ContractOrSourceId; + contract: Contract; /** * Marlowe contracts can have staking rewards for the ADA locked in the contract. @@ -61,7 +57,7 @@ export type CreateContractRequest = { threadRoleName?: RoleName; /** - * Role Token Configuration for the new contract passed in the `contractOrSourceId` field. + * Role Token Configuration for the new contract passed in the `contract` field. * *

Participants

*

@@ -80,7 +76,7 @@ export type CreateContractRequest = { * - **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 `contractOrSourceId` 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. + * - **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. * diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts index 5b8e9ab8..2936cce7 100644 --- a/packages/runtime/lifecycle/src/generic/contracts.ts +++ b/packages/runtime/lifecycle/src/generic/contracts.ts @@ -158,7 +158,7 @@ export const submitCreateTxFpTs: ( collateralUTxOs: addressesAndCollaterals.collateralUTxOs, stakeAddress: createContractRequest.stakeAddress, - contractOrSourceId: createContractRequest.contractOrSourceId, + contractOrSourceId: createContractRequest.contract, threadRoleName: createContractRequest.threadRoleName, roles: createContractRequest.roles, 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 9b10e663..a0a2d43c 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 @@ -65,7 +65,7 @@ describe("swap", () => { const [contractId, txIdContractCreated] = await runtime( adaProvider ).contracts.createContract({ - contractOrSourceId: swapContract, + contract: swapContract, roles: { [swapRequest.provider.roleName]: mintRole( adaProvider.address as unknown as AddressBech32 diff --git a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts index f86a102f..644bfb7f 100644 --- a/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts +++ b/packages/runtime/lifecycle/test/generic/contracts.e2e.spec.ts @@ -33,7 +33,7 @@ describe("Runtime Contract Lifecycle ", () => { getBankPrivateKey() ); const [contractId, txIdContractCreated] = - await runtime.contracts.createContract({ contractOrSourceId: close }); + await runtime.contracts.createContract({ contract: close }); await runtime.wallet.waitConfirmation(txIdContractCreated); console.log("contractID created", contractId); }, @@ -51,7 +51,7 @@ describe("Runtime Contract Lifecycle ", () => { const notifyTimeout = pipe(addDays(Date.now(), 1), datetoTimeout); const [contractId, txIdContractCreated] = await runtime.contracts.createContract({ - contractOrSourceId: oneNotifyTrue(notifyTimeout), + contract: oneNotifyTrue(notifyTimeout), }); await runtime.wallet.waitConfirmation(txIdContractCreated); diff --git a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts b/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts index a0e14ec6..c9fcb6cf 100644 --- a/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts +++ b/packages/runtime/lifecycle/test/generic/payouts.e2e.spec.ts @@ -52,7 +52,7 @@ describe("Payouts", () => { const [contractId, txCreatedContract] = await runtime( adaProvider ).contracts.createContract({ - contractOrSourceId: swapContract, + contract: swapContract, roles: { [swapRequest.provider.roleName]: mintRole( adaProvider.address as unknown as AddressBech32 From 07222694c9ef2ce6dc00070546ed2174f46edf17 Mon Sep 17 00:00:00 2001 From: nhenin Date: Thu, 7 Dec 2023 17:56:09 +0100 Subject: [PATCH 11/13] sum type for BuildCreateContractTxRequest --- .../rest/src/contract/endpoints/collection.ts | 44 +++++++++++++++---- .../runtime/client/rest/src/contract/index.ts | 3 ++ packages/runtime/client/rest/src/index.ts | 3 +- .../lifecycle/src/generic/contracts.ts | 2 +- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/runtime/client/rest/src/contract/endpoints/collection.ts b/packages/runtime/client/rest/src/contract/endpoints/collection.ts index 4ba3cc6a..e2ac2b38 100644 --- a/packages/runtime/client/rest/src/contract/endpoints/collection.ts +++ b/packages/runtime/client/rest/src/contract/endpoints/collection.ts @@ -213,6 +213,29 @@ export const ContractOrSourceIdGuard: t.Type = t.union([ SourceIdGuard, ]); +/** + * Request for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint using a source Id (merkleized contract) + * @category Endpoint : Build Create Contract Tx + */ +export type BuildCreateContractTxRequestWithContract = { + /** + * A Marlowe Contract to create over Cardano + */ + contract: Contract; +} & BuildCreateContractTxRequestOptions; + +/** + * Request for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint using a contract + * @category Endpoint : Build Create Contract Tx + */ +export type BuildCreateContractTxRequestWithSourceId = { + /** + * A merkleized Contract (referred by its source Id) to create over Cardano + * @see Large/Deep Contracts Support (Contract Merkleization) and `@marlowe.io/language-core` + */ + sourceId: SourceId; +} & BuildCreateContractTxRequestOptions; + /** * Request options for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint * @category Endpoint : Build Create Contract Tx @@ -220,7 +243,7 @@ export const ContractOrSourceIdGuard: t.Type = t.union([ * - Minimal Simple Contract Close * ```json * { "changeAddress" : "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja", - * "contractOrSourceId" : "close", + * "contract" : "close", * "tags" : {"ts-sdk.documentation.example" : {"infoA" : 5} }, * "version" : "v1" * } @@ -230,7 +253,7 @@ export const ContractOrSourceIdGuard: t.Type = t.union([ * { "changeAddress" : "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja", * "usedAddresses": ["addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja"], * "collateralUTxOs": [], - * "contractOrSourceId" : "close", + * "contract" : "close", * "tags" : {"ts-sdk.documentation.example" : {"infoA" : 5} }, * "minimumLovelaceUTxODeposit" : 3000000, * "threadRoleName" : "ThreadRoleToken", @@ -242,7 +265,7 @@ export const ContractOrSourceIdGuard: t.Type = t.union([ * { "changeAddress" : "addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja", * "usedAddresses": ["addr_test1qqe342swyfn75mp2anj45f8ythjyxg6m7pu0pznptl6f2d84kwuzrh8c83gzhrq5zcw7ytmqc863z5rhhwst3w4x87eq0td9ja"], * "collateralUTxOs": [], - * "contractOrSourceId" : {"when":[{"then":{"when":[{"then":{"token":{"token_name":"","currency_symbol":""},"to":{"party":{"role_token":"Dollar provider"}},"then":{"token":{"token_name":"dollar","currency_symbol":"85bb65"},"to":{"party":{"role_token":"Ada provider"}},"then":"close","pay":0,"from_account":{"role_token":"Dollar provider"}},"pay":{"times":0,"multiply":1000000},"from_account":{"role_token":"Ada provider"}},"case":{"party":{"role_token":"Dollar provider"},"of_token":{"token_name":"dollar","currency_symbol":"85bb65"},"into_account":{"role_token":"Dollar provider"},"deposits":0}}],"timeout_continuation":"close","timeout":1701773934770},"case":{"party":{"role_token":"Ada provider"},"of_token":{"token_name":"","currency_symbol":""},"into_account":{"role_token":"Ada provider"},"deposits":{"times":0,"multiply":1000000}}}],"timeout_continuation":"close","timeout":1701772134770}, + * "contract" : {"when":[{"then":{"when":[{"then":{"token":{"token_name":"","currency_symbol":""},"to":{"party":{"role_token":"Dollar provider"}},"then":{"token":{"token_name":"dollar","currency_symbol":"85bb65"},"to":{"party":{"role_token":"Ada provider"}},"then":"close","pay":0,"from_account":{"role_token":"Dollar provider"}},"pay":{"times":0,"multiply":1000000},"from_account":{"role_token":"Ada provider"}},"case":{"party":{"role_token":"Dollar provider"},"of_token":{"token_name":"dollar","currency_symbol":"85bb65"},"into_account":{"role_token":"Dollar provider"},"deposits":0}}],"timeout_continuation":"close","timeout":1701773934770},"case":{"party":{"role_token":"Ada provider"},"of_token":{"token_name":"","currency_symbol":""},"into_account":{"role_token":"Ada provider"},"deposits":{"times":0,"multiply":1000000}}}],"timeout_continuation":"close","timeout":1701772134770}, * "tags" : {"ts-sdk.documentation.example" : {"infoA" : 5} }, * "roles" : {"Ada provider" : {"recipients": {"OpenRole" : 1} } * ,"Dollar provider" : {"recipients": {"OpenRole" : 1} } }, @@ -252,7 +275,15 @@ export const ContractOrSourceIdGuard: t.Type = t.union([ * } * ``` */ -export interface BuildCreateContractTxRequest { +export type BuildCreateContractTxRequest = + | BuildCreateContractTxRequestWithContract + | BuildCreateContractTxRequestWithSourceId; + +/** + * Request options for the {@link index.RestClient#buildCreateContractTx | Build Create Contract Tx } endpoint + * @category Endpoint : Build Create Contract Tx + */ +export interface BuildCreateContractTxRequestOptions { /** * Marlowe contracts can have staking rewards for the ADA locked in the contract. * Use this field to set the recipient address of those rewards @@ -306,11 +337,6 @@ export interface BuildCreateContractTxRequest { */ collateralUTxOs?: TxOutRef[]; - /** - * A Marlowe Contract or a Merkleized One (referred by its source Id) to create over Cardano - * @see Large/Deep Contracts Support (Contract Merkleization) and `@marlowe.io/language-core` - */ - contractOrSourceId: ContractOrSourceId; /** * Marlowe Tags are stored as Metadata within the Transaction Metadata under the top-level Marlowe Reserved Key (`1564`). * Tags allows to Query created Marlowe Contracts via {@link index.RestClient#getContracts | Get contracts } diff --git a/packages/runtime/client/rest/src/contract/index.ts b/packages/runtime/client/rest/src/contract/index.ts index d21740e6..be97e836 100644 --- a/packages/runtime/client/rest/src/contract/index.ts +++ b/packages/runtime/client/rest/src/contract/index.ts @@ -38,6 +38,9 @@ export { ContractsRange, ContractOrSourceId, BuildCreateContractTxRequest, + BuildCreateContractTxRequestWithContract, + BuildCreateContractTxRequestWithSourceId, + BuildCreateContractTxRequestOptions, BuildCreateContractTxResponse, } from "./endpoints/collection.js"; export { TxHeader } from "./transaction/header.js"; diff --git a/packages/runtime/client/rest/src/index.ts b/packages/runtime/client/rest/src/index.ts index 44c2d495..35a2d11e 100644 --- a/packages/runtime/client/rest/src/index.ts +++ b/packages/runtime/client/rest/src/index.ts @@ -41,6 +41,7 @@ import { submitContractViaAxios } from "./contract/endpoints/singleton.js"; import { ContractDetails } from "./contract/details.js"; import { TransactionDetails } from "./contract/transaction/details.js"; import { CreateContractSourcesResponse } from "./contract/endpoints/sources.js"; +import { BuildCreateContractTxRequestWithContract } from "./contract/index.js"; // import curlirize from 'axios-curlirize'; /** @@ -249,7 +250,7 @@ export function mkRestClient(baseURL: string): RestClient { }, buildCreateContractTx(request) { const postContractsRequest = { - contract: request.contractOrSourceId, + contract: "contract" in request ? request.contract : request.sourceId, version: request.version, metadata: request.metadata ?? {}, tags: request.tags ?? {}, diff --git a/packages/runtime/lifecycle/src/generic/contracts.ts b/packages/runtime/lifecycle/src/generic/contracts.ts index 2936cce7..2cf72600 100644 --- a/packages/runtime/lifecycle/src/generic/contracts.ts +++ b/packages/runtime/lifecycle/src/generic/contracts.ts @@ -158,7 +158,7 @@ export const submitCreateTxFpTs: ( collateralUTxOs: addressesAndCollaterals.collateralUTxOs, stakeAddress: createContractRequest.stakeAddress, - contractOrSourceId: createContractRequest.contract, + contract: createContractRequest.contract, threadRoleName: createContractRequest.threadRoleName, roles: createContractRequest.roles, From abaa444592ed7d69db91c3d1f000eadebc4e256c Mon Sep 17 00:00:00 2001 From: nhenin Date: Fri, 8 Dec 2023 09:56:33 +0100 Subject: [PATCH 12/13] updated flock.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 70f303e0..da1c7987 100644 --- a/flake.lock +++ b/flake.lock @@ -119,11 +119,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701697687, - "narHash": "sha256-dLLE5wQBVv+pIb4bWmKFSw2DvLVyuEk0F7ng6hpZPSU=", + "lastModified": 1701787589, + "narHash": "sha256-ce+oQR4Zq9VOsLoh9bZT8Ip9PaMLcjjBUHVPzW5d7Cw=", "owner": "numtide", "repo": "devshell", - "rev": "c3bd77911391eb1638af6ce773de86da57ee6df5", + "rev": "44ddedcbcfc2d52a76b64fb6122f209881bd3e1e", "type": "github" }, "original": { From 09fc3cd57763eaf429545f9b5f72b8081f4f2a55 Mon Sep 17 00:00:00 2001 From: nhenin Date: Fri, 8 Dec 2023 09:58:48 +0100 Subject: [PATCH 13/13] updated changelog --- changelog.d/20231208_095010_nicolas.henin.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog.d/20231208_095010_nicolas.henin.md diff --git a/changelog.d/20231208_095010_nicolas.henin.md b/changelog.d/20231208_095010_nicolas.henin.md new file mode 100644 index 00000000..60acd135 --- /dev/null +++ b/changelog.d/20231208_095010_nicolas.henin.md @@ -0,0 +1,8 @@ +### @marlowe.io/runtime-rest-client + +- `createContract` Endpoint has been renamed to `buildCreateContractTx` ([PR#54](https://github.com/input-output-hk/marlowe-ts-sdk/pull/54)) +- `buildCreateContractTx` is 1-1 parity with REST API request-wise ([PR#54](https://github.com/input-output-hk/marlowe-ts-sdk/pull/54)) + +### @marlowe.io/runtime-lifecycle + +- `createContract` is complete request-wise for creating non-merkleized contracts ([PR#54](https://github.com/input-output-hk/marlowe-ts-sdk/pull/54))