Skip to content

Commit

Permalink
Modify marlowe-object-flow to use Blueprint
Browse files Browse the repository at this point in the history
  • Loading branch information
hrajchert committed Feb 13, 2024
1 parent c09a83e commit d22ed40
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 143 deletions.
148 changes: 61 additions & 87 deletions examples/nodejs/src/marlowe-object-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import { Lucid, Blockfrost, C } from "lucid-cardano";
import { readConfig } from "./config.js";
import { datetoTimeout, When } from "@marlowe.io/language-core-v1";
import {
addressBech32,
contractId,
ContractId,
Metadata,
stakeAddressBech32,
StakeAddressBech32,
Tags,
Expand All @@ -38,6 +40,7 @@ import { mkSourceMap, SourceMap } from "./experimental-features/source-map.js";
import { POSIXTime, posixTimeToIso8601 } from "@marlowe.io/adapter/time";
import { SingleInputTx } from "@marlowe.io/language-core-v1/semantics";
import * as ObjG from "@marlowe.io/marlowe-object/guards";
import { BlueprintOf, mkBlueprint } from "@marlowe.io/blueprint";

// When this script is called, start with main.
main();
Expand Down Expand Up @@ -139,10 +142,12 @@ async function createContractMenu(
lifecycle: RuntimeLifecycle,
rewardAddress?: StakeAddressBech32
) {
const payee = await input({
message: "Enter the payee address",
validate: bech32Validator,
});
const payee = addressBech32(
await input({
message: "Enter the payee address",
validate: bech32Validator,
})
);
const amountStr = await input({
message: "Enter the payment amount in lovelaces",
validate: positiveBigIntValidator,
Expand Down Expand Up @@ -176,17 +181,18 @@ async function createContractMenu(
}

const scheme = {
payFrom: { address: walletAddress },
payTo: { address: payee },
payer: walletAddress,
payee,
amount,
depositDeadline,
releaseDeadline,
};
const tags = mkDelayPaymentTags(scheme);
const metadata = delayPaymentBlueprint.encode(scheme);
const sourceMap = await mkSourceMap(lifecycle, mkDelayPayment(scheme));
const [contractId, txId] = await sourceMap.createContract({
stakeAddress: rewardAddress,
tags,
tags: { DELAY_PAYMENT_VERSION: "2" },
metadata,
});

console.log(`Contract created with id ${contractId}`);
Expand All @@ -209,7 +215,7 @@ async function loadContractMenu(lifecycle: RuntimeLifecycle) {
const cid = contractId(cidStr);
// Then we make sure that contract id is an instance of our delayed payment contract
const validationResult = await validateExistingContract(lifecycle, cid);
if (validationResult === "InvalidTags") {
if (validationResult === "InvalidBlueprint") {
console.log("Invalid contract, it does not have the expected tags");
return;
}
Expand All @@ -222,8 +228,8 @@ async function loadContractMenu(lifecycle: RuntimeLifecycle) {

// If it is, we print the contract details and go to the contract menu
console.log("Contract details:");
console.log(` * Pay from: ${validationResult.scheme.payFrom.address}`);
console.log(` * Pay to: ${validationResult.scheme.payTo.address}`);
console.log(` * Pay from: ${validationResult.scheme.payer}`);
console.log(` * Pay to: ${validationResult.scheme.payee}`);
console.log(` * Amount: ${validationResult.scheme.amount} lovelaces`);
console.log(
` * Deposit deadline: ${validationResult.scheme.depositDeadline}`
Expand Down Expand Up @@ -374,32 +380,46 @@ async function mainLoop(
// #endregion

// #region Delay Payment Contract

const delayPaymentBlueprint = mkBlueprint({
name: "Delayed payment",
description:
"In a delay payment, a `payer` transfer an `amount` of ADA to the `payee` which can be redeemed after a `releaseDeadline`. While the payment is held by the contract, it can be staked to the payer, to generate pasive income while the payee has the guarantees that the money will be released.",
params: [
{
name: "payer",
description: "Who is making the payment",
type: "address",
},
{
name: "payee",
description: "Who is receiving the payment",
type: "address",
},
{
name: "amount",
description: "The amount of lovelaces to be paid",
type: "value",
},
{
name: "depositDeadline",
description:
"The deadline for the payment to be made. If the payment is not made by this date, the contract can be closed",
type: "date",
},
{
name: "releaseDeadline",
description:
"A date after the payment can be released to the receiver. NOTE: An empty transaction must be done to close the contract",
type: "date",
},
] as const,
});

/**
* These are the parameters of the contract
*/
interface DelayPaymentScheme {
/**
* Who is making the delayed payment
*/
payFrom: Address;
/**
* Who is receiving the payment
*/
payTo: Address;
/**
* The amount of lovelaces to be paid
*/
amount: bigint;
/**
* The deadline for the payment to be made. If the payment is not made by this date, the contract can be closed
*/
depositDeadline: Date;
/**
* A date after the payment can be released to the receiver.
* NOTE: An empty transaction must be done to close the contract
*/
releaseDeadline: Date;
}
type DelayPaymentScheme = BlueprintOf<typeof delayPaymentBlueprint>;

type DelayPaymentAnnotations =
| "initialDeposit"
Expand Down Expand Up @@ -436,10 +456,10 @@ function mkDelayPayment(
when: [
{
case: {
party: scheme.payFrom,
deposits: scheme.amount,
party: { address: scheme.payer },
deposits: BigInt(scheme.amount),
of_token: lovelace,
into_account: scheme.payTo,
into_account: { address: scheme.payee },
},
then: {
ref: "release-funds",
Expand Down Expand Up @@ -503,9 +523,7 @@ type Closed = {
function printState(state: DelayPaymentState, scheme: DelayPaymentScheme) {
switch (state.type) {
case "InitialState":
console.log(
`Waiting ${scheme.payFrom.address} to deposit ${scheme.amount}`
);
console.log(`Waiting ${scheme.payer} to deposit ${scheme.amount}`);
break;
case "PaymentDeposited":
console.log(
Expand Down Expand Up @@ -562,52 +580,8 @@ function getState(

// #endregion

const mkDelayPaymentTags = (schema: DelayPaymentScheme) => {
const tag = "DELAY_PYMNT-1";
const tags = {} as Tags;

tags[`${tag}-from-0`] = splitAddress(schema.payFrom)[0];
tags[`${tag}-from-1`] = splitAddress(schema.payFrom)[1];
tags[`${tag}-to-0`] = splitAddress(schema.payTo)[0];
tags[`${tag}-to-1`] = splitAddress(schema.payTo)[1];
tags[`${tag}-amount`] = schema.amount;
tags[`${tag}-deposit`] = schema.depositDeadline;
tags[`${tag}-release`] = schema.releaseDeadline;
return tags;
};

const extractSchemeFromTags = (
tags: unknown
): DelayPaymentScheme | undefined => {
const tagsGuard = t.type({
"DELAY_PYMNT-1-from-0": t.string,
"DELAY_PYMNT-1-from-1": t.string,
"DELAY_PYMNT-1-to-0": t.string,
"DELAY_PYMNT-1-to-1": t.string,
"DELAY_PYMNT-1-amount": t.bigint,
"DELAY_PYMNT-1-deposit": t.string,
"DELAY_PYMNT-1-release": t.string,
});

if (!tagsGuard.is(tags)) {
return;
}

return {
payFrom: {
address: `${tags["DELAY_PYMNT-1-from-0"]}${tags["DELAY_PYMNT-1-from-1"]}`,
},
payTo: {
address: `${tags["DELAY_PYMNT-1-to-0"]}${tags["DELAY_PYMNT-1-to-1"]}`,
},
amount: tags["DELAY_PYMNT-1-amount"],
depositDeadline: new Date(tags["DELAY_PYMNT-1-deposit"]),
releaseDeadline: new Date(tags["DELAY_PYMNT-1-release"]),
};
};

type ValidationResults =
| "InvalidTags"
| "InvalidBlueprint"
| "InvalidContract"
| {
scheme: DelayPaymentScheme;
Expand All @@ -629,10 +603,10 @@ async function validateExistingContract(
contractId,
});

const scheme = extractSchemeFromTags(contractDetails.tags);
const scheme = delayPaymentBlueprint.decode(contractDetails.metadata);

if (!scheme) {
return "InvalidTags";
return "InvalidBlueprint";
}

// If the contract seems to be an instance of the contract we want (meanin, we were able
Expand Down
2 changes: 2 additions & 0 deletions jsdelivr-npm-importmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const importMap = {
"https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta/dist/bundled/esm/lucid.js",
"@marlowe.io/adapter/time":
"https://cdn.jsdelivr.net/npm/@marlowe.io/adapter@0.3.0-beta/dist/bundled/esm/time.js",
"@marlowe.io/blueprint":
"https://cdn.jsdelivr.net/npm/@marlowe.io/blueprint@0.3.0-beta/dist/bundled/esm/blueprint.js",
"@marlowe.io/language-core-v1":
"https://cdn.jsdelivr.net/npm/@marlowe.io/language-core-v1@0.3.0-beta/dist/bundled/esm/language-core-v1.js",
"@marlowe.io/language-core-v1/guards":
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"workspaces": [
"packages/adapter",
"packages/blueprint",
"packages/language/core/v1",
"packages/language/examples",
"packages/language/specification-client",
Expand Down
3 changes: 3 additions & 0 deletions packages/blueprint/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Marlowe Blueprint

TODO.
51 changes: 1 addition & 50 deletions packages/blueprint/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1 @@
interface ContractParameter {
name: string;
description?: string;
}

interface AddressParameter extends ContractParameter {
type: "address";
}

interface DateParameter extends ContractParameter {
type: "date";
}

interface ValueParameter extends ContractParameter {
type: "value";
}

type BlueprintParameter = AddressParameter | DateParameter | ValueParameter;

type Blueprint = BlueprintParameter[];

const delayPaymentBlueprint: Blueprint = [
{
name: "payFrom",
description: "Who is making the delayed payment",
type: "address",
},
{
name: "payTo",
description: "Who is receiving the payment",
type: "address",
},
{
name: "amount",
description: "The amount of lovelaces to be paid",
type: "value",
},
{
name: "depositDeadline",
description:
"The deadline for the payment to be made. If the payment is not made by this date, the contract can be closed",
type: "date",
},
{
name: "releaseDeadline",
description:
"A date after the payment can be released to the receiver. NOTE: An empty transaction must be done to close the contract",
type: "date",
},
];
export { Blueprint, mkBlueprint, MkBlueprint, BlueprintOf } from "./typed.js";
13 changes: 8 additions & 5 deletions packages/blueprint/src/typed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
AddressBech32,
AddressBech32Guard,
Metadata,
MetadatumGuard,
} from "@marlowe.io/runtime-core";
import {
BigIntOrNumber,
Expand Down Expand Up @@ -93,7 +92,7 @@ function blueprintParamsObjectGuard<T extends readonly BlueprintParam<any>[]>(
);
}

class Blueprint<T extends object> {
export class Blueprint<T extends object> {
private blueprintCodec: t.Type<T, t.OutputOf<typeof Metadata>, unknown>;
name: string;
description?: string;
Expand Down Expand Up @@ -198,9 +197,13 @@ class Blueprint<T extends object> {
throw new Error("Invalid value");
}
}

encode(value: T): t.OutputOf<typeof Metadata> {
return this.blueprintCodec.encode(value);
// NOTE: The output of the encode is the output of the Metadata codec,
// which is compatible with its inputs, but it doesn't have the
// branded types. Here we cast to the Metadata type to allow easier
// usability with the rest of the runtime, that expects the Branded
// types.
encode(value: T): Metadata {
return this.blueprintCodec.encode(value) as Metadata;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/blueprint/test/blueprint-delayed-payment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AddressBech32, addressBech32 } from "@marlowe.io/runtime-core";
import { BlueprintOf, mkBlueprint } from "../src/typed.js";
import { BlueprintOf, mkBlueprint } from "@marlowe.io/blueprint";
describe("Delayed payment Blueprint", () => {
const delayPaymentBlueprint = mkBlueprint({
name: "Delayed payment",
Expand Down
Loading

0 comments on commit d22ed40

Please sign in to comment.