-
Notifications
You must be signed in to change notification settings - Fork 10
/
applicable-actions.ts
753 lines (687 loc) · 23.9 KB
/
applicable-actions.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
import { ApplyInputsRequest, ContractsAPI, applyInputs } from "./contracts.js";
import { Monoid } from "fp-ts/lib/Monoid.js";
import * as R from "fp-ts/lib/Record.js";
import {
MarloweState,
Party,
Contract,
Deposit,
Choice,
Input,
ChosenNum,
Environment,
Timeout,
getNextTimeout,
datetoTimeout,
Case,
Action,
Notify,
InputContent,
RoleName,
Token,
Bound,
BuiltinByteString,
} from "@marlowe.io/language-core-v1";
import {
computeTransaction,
evalObservation,
evalValue,
inBounds,
reduceContractUntilQuiescent,
TransactionSuccess,
} from "@marlowe.io/language-core-v1/semantics";
import {
AddressBech32,
ContractId,
Metadata,
PolicyId,
Tags,
TxId,
} from "@marlowe.io/runtime-core";
import { RestClient, RestDI, Tip } from "@marlowe.io/runtime-rest-client";
import { WalletAPI, WalletDI } from "@marlowe.io/wallet";
import * as Big from "@marlowe.io/adapter/bigint";
import { ContractSourceId } from "@marlowe.io/marlowe-object";
import { posixTimeToIso8601 } from "@marlowe.io/adapter/time";
import { ActiveContract, ContractDetails } from "./new-contract-api.js";
/**
* @experimental
* @category ApplicableActionsAPI
*/
export interface ApplicableActionsAPI {
/**
* Get what actions can be applied to a contract in a given environment.
* @param contractId The id of a Marlowe contract
* @param environment An optional interval with the time bounds to apply the actions. If none is provided
* the environment is computed using the runtime tip as a lower bound and the next timeout
* as an upper bound.
* @returns An object with an array of {@link ApplicableAction | applicable actions} and the {@link ContractDetails | contract details}
* @experimental
* @remarks
* **EXPERIMENTAL:** Perhaps instead of receiving a contractId and returning the actions and contractDetails this
* function should receive the contractDetails and just return the actions.
* To do this, we should refactor the {@link ContractsAPI} first to use the {@link ContractDetails} type
*/
getApplicableActions(
contractDetails: ContractDetails,
environment?: Environment
): Promise<ApplicableAction[]>;
/**
* Converts an {@link ApplicableAction} into an {@link ApplicableInput}.
*
* This function has two {@link https://www.tutorialsteacher.com/typescript/function-overloading | overloads}. This
* one can be used with {@link @marlowe.io/language-core-v1!index.Notify},
* {@link @marlowe.io/language-core-v1!index.Deposit} actions, or to Advance a timed out
* contract, none of which require further information.
*/
getInput(contractDetails: ActiveContract, action: CanNotify | CanDeposit | CanAdvance): Promise<ApplicableInput>;
/**
* Converts an {@link ApplicableAction} into an {@link ApplicableInput}.
*
* This function has two {@link https://www.tutorialsteacher.com/typescript/function-overloading | overloads}. This
* one can be used with the Choose action, which requires the chosen number.
*/
getInput(contractDetails: ActiveContract, action: CanChoose, chosenNum: ChosenNum): Promise<ApplicableInput>;
/**
* Applies an input to a contract. This is a wrapper around the {@link ContractsAPI.applyInputs | Contracts's API applyInputs} function.
*/
applyInput(contractId: ContractId, request: ApplyApplicableInputRequest): Promise<TxId>;
/**
* Simulates the result of applying an {@link ApplicableInput}. The input should be obtained by
* {@link getApplicableActions | computing the applicable actions} and then {@link getInput | converting them into an input}.
* @returns If the input was obtained by the described flow, it is guaranteed to return a {@link TransactionSuccess} with
* the payments, state, warnings and new continuation.
*/
simulateInput(contractDetails: ActiveContract, input: ApplicableInput): TransactionSuccess;
/**
* Creates a filter function for the {@link ApplicableAction | applicable actions} of the wallet owner.
* The wallet is configured when we instantiate the {@link RuntimeLifecycle}. This function returns a new
* filter function when called multiple times. This is useful to update the filter to check for new Role tokens
* available in the wallet.
*
* The function has two {@link https://www.tutorialsteacher.com/typescript/function-overloading | overloads}. This
* one is appied with the contract details and it is useful when filtering the actions of a specific contract.
*/
mkFilter(contractDetails: ActiveContract): Promise<ApplicableActionsFilter>;
/**
* Creates a filter function for the {@link ApplicableAction | applicable actions} of the wallet owner.
* The wallet is configured when we instantiate the {@link RuntimeLifecycle}. This function returns a new
* filter function when called multiple times. This is useful to update the filter to check for new Role tokens
* available in the wallet.
*
* The function has two {@link https://www.tutorialsteacher.com/typescript/function-overloading | overloads}. This
* one does not receive any argument and it is useful when filtering the actions of multiple contracts.
* Notice that the {@link ApplicableActionsWithDetailsFilter | filter function} returned by this overload will require the contract details to be passed as
* a second argument.
*/
mkFilter(): Promise<ApplicableActionsWithDetailsFilter>;
}
/**
*
* @category ApplicableActionsAPI
*/
export interface ApplyApplicableInputRequest {
input: ApplicableInput;
tags?: Tags;
metadata?: Metadata;
}
/**
* @hidden
*/
export function mkApplicableActionsAPI(
di: RestDI & WalletDI
): ApplicableActionsAPI {
const getApplicableActionsDI = mkGetApplicableActionsDI(di.restClient);
async function mkFilter(): Promise<ApplicableActionsWithDetailsFilter>;
async function mkFilter(contractDetails: ActiveContract): Promise<ApplicableActionsFilter>;
async function mkFilter(
contractDetails?: ActiveContract
): Promise<ApplicableActionsFilter | ApplicableActionsWithDetailsFilter> {
const curriedFilter = await mkApplicableActionsFilter(di.wallet);
if (contractDetails) {
return (action: ApplicableAction) => curriedFilter(action, contractDetails);
} else {
return curriedFilter;
}
}
return {
getInput: getApplicableInput(getApplicableActionsDI),
simulateInput: simulateApplicableInput,
getApplicableActions: getApplicableActions(getApplicableActionsDI),
applyInput: applyInput(applyInputs(di)),
mkFilter,
};
}
export type ApplyInputDI = (
contractId: ContractId,
request: ApplyInputsRequest
) => Promise<TxId>;
function applyInput(doApply: ApplyInputDI) {
return async function (
contractId: ContractId,
request: ApplyApplicableInputRequest
): Promise<TxId> {
return doApply(contractId, {
inputs: request.input.inputs,
tags: request.tags,
metadata: request.metadata,
invalidBefore: posixTimeToIso8601(request.input.environment.timeInterval.from),
// NOTE: This is commented out because the end time of the interval might be
// way into the future and the time to slot conversion is undefined if the
// end time passes a certain threshold.
// We currently don't have the network parameters to do the conversion ourselves
// so we leave to the runtime to calculate an adecuate max slot.
// This might cause some issues if the contract relies on the TimeIntervalEnd value
// as the result of simulating and applying the input might differ.
// invalidHereafter: posixTimeToIso8601(
// request.input.environment.timeInterval.to
// ),
});
};
}
type ActionApplicant = Party | "anybody";
/**
* @category ApplicableActionsAPI
*/
export interface ApplicableInput {
/**
* What inputs needs to be provided to apply the action
*/
inputs: Input[];
/**
* What is the environment to apply the inputs
*/
environment: Environment;
}
/**
* This data structure indicates that the contract can be notified.
* @category ApplicableActionsAPI
*/
export interface CanNotify {
/**
* Discriminator field, used to differentiate the action type.
*/
actionType: "Notify";
/**
* If the When's case is merkleized, this is the hash of the merkleized continuation.
*/
merkleizedContinuationHash?: BuiltinByteString;
/**
* The action can be applied in this environment.
*/
environment: Environment;
}
/**
* This data structure indicates that the contract can receive a deposit.
* @category ApplicableActionsAPI
*/
export interface CanDeposit {
/**
* Discriminator field, used to differentiate the action type.
*/
actionType: "Deposit";
/**
* If the When's case is merkleized, this is the hash of the merkleized continuation.
*/
merkleizedContinuationHash?: BuiltinByteString;
/**
* The deposit action that can be applied.
*/
deposit: Deposit;
/**
* The action can be applied in this environment.
*/
environment: Environment;
}
/**
* This data structure indicates that the contract can receive a Choice action.
* @category ApplicableActionsAPI
*/
export interface CanChoose {
/**
* Discriminator field, used to differentiate the action type.
*/
actionType: "Choice";
/**
* If the When's case is merkleized, this is the hash of the merkleized continuation.
*/
merkleizedContinuationHash?: BuiltinByteString;
/**
* The choice action that can be applied.
*/
choice: Choice;
/**
* The action can be applied in this environment.
*/
environment: Environment;
}
/**
* This data structure indicates that the contract is timed out and can be advanced to it's continuation.
* If the timeout continuation is a Close contract, then this is the only way to close it. If the continuation
* has a When with some actions, then the contract can either be advanced to the next When, or one of the
* next actions can be applied.
* @category ApplicableActionsAPI
*/
export interface CanAdvance {
actionType: "Advance";
environment: Environment;
}
/**
* Represents what actions can be applied to a contract in a given environment.
* @category ApplicableActionsAPI
*/
export type ApplicableAction = CanNotify | CanDeposit | CanChoose | CanAdvance;
/**
* @hidden
*/
export function getApplicableInput(di: GetContinuationDI) {
async function doMakeApplicableInput(
contractDetails: ActiveContract,
action: CanNotify | CanDeposit | CanAdvance
): Promise<ApplicableInput>;
async function doMakeApplicableInput(
contractDetails: ActiveContract,
action: CanChoose,
chosenNum: ChosenNum
): Promise<ApplicableInput>;
async function doMakeApplicableInput(
contractDetails: ActiveContract,
action: ApplicableAction,
chosenNum?: ChosenNum
): Promise<ApplicableInput>;
async function doMakeApplicableInput(
contractDetails: ActiveContract,
action: ApplicableAction,
chosenNum?: ChosenNum
): Promise<ApplicableInput> {
async function decorateInput(
content: InputContent,
merkleizedContinuationHash?: BuiltinByteString
): Promise<Input> {
if (merkleizedContinuationHash) {
const aCaseContinuation = await di.getContractContinuation(merkleizedContinuationHash);
const merkleizedHashAndContinuation = {
continuation_hash: merkleizedContinuationHash,
merkleized_continuation: aCaseContinuation,
};
// MerkleizedNotify are serialized as the plain merkle object
if (content === "input_notify") {
return merkleizedHashAndContinuation;
} else {
// For IDeposit and IChoice is the InputContent + the merkle object
return {
...merkleizedHashAndContinuation,
...content,
};
}
} else {
return content;
}
}
const environment = action.environment;
switch (action.actionType) {
case "Advance":
return {
inputs: [],
environment,
};
case "Deposit":
const deposit = action.deposit;
const depositInput = await decorateInput(
{
input_from_party: deposit.party,
// TODO: Check and add a test wether this should be the state as given by the contractDetails endpoint
// or the result of reducing it.
that_deposits: evalValue(environment, contractDetails.currentState, deposit.deposits),
of_token: deposit.of_token,
into_account: deposit.into_account,
},
action.merkleizedContinuationHash
);
return {
inputs: [depositInput],
environment,
};
case "Notify":
const notifyInput = await decorateInput("input_notify", action.merkleizedContinuationHash);
return {
inputs: [notifyInput],
environment,
};
case "Choice":
const choice = action.choice;
// TODO: Improve errors
if (!chosenNum) {
throw new Error("Chosen number is not provided");
}
if (!inBounds(chosenNum, choice.choose_between)) {
throw new Error("Chosen number is not in bounds");
}
const choiceInput = await decorateInput(
{
for_choice_id: choice.for_choice,
input_that_chooses_num: chosenNum,
},
action.merkleizedContinuationHash
);
return {
inputs: [choiceInput],
environment,
};
}
}
return doMakeApplicableInput;
}
/**
* @hidden
*/
export function simulateApplicableInput(
contractDetails: ActiveContract,
applicableInput: ApplicableInput
): TransactionSuccess {
const txOut = computeTransaction(
{
tx_interval: applicableInput.environment.timeInterval,
tx_inputs: applicableInput.inputs,
},
contractDetails.currentState,
contractDetails.currentContract
);
if ("transaction_error" in txOut) {
// TODO: Improve error
throw new Error("There was a transaction error" + txOut.transaction_error);
}
return txOut;
}
function getApplicant(action: ApplicableAction): ActionApplicant {
switch (action.actionType) {
case "Notify":
case "Advance":
return "anybody";
case "Deposit":
return action.deposit.party;
case "Choice":
return action.choice.for_choice.choice_owner;
}
}
type ChainTipDI = {
getRuntimeTip: () => Promise<Date>;
};
/**
* Computes a "safe" environment for the contract.
* @hidden
*/
async function computeEnvironment({ getRuntimeTip }: ChainTipDI, currentContract: Contract): Promise<Environment> {
const oneDayFrom = (time: Timeout) => time + 24n * 60n * 60n * 1000n; // in milliseconds
// For the lower, bound we use the tip of the runtime chain.
// If we used new Date(), the runtime can complain that the lower bound is too high
// because it is ahead of the time that it knows.
const tip = await getRuntimeTip();
const lowerBound = datetoTimeout(tip);
// For the upper bound, we use the next timeout if available or one day from the lower bound.
// IMPORTANT NOTE: With this code, if the upper bound is far into the future, and this interval
// is used when applying an input, you can end up with a Slot conversion error.
// This is because the ledger can change the slot length at particular times
// so the runtime cannot predict what is the exact slot.
// The safe way to solve this is to get the network parameters from the runtime
// and instead of doing oneDayFrom, do the max safe conversion.
const upperBound = getNextTimeout(currentContract, lowerBound) ?? oneDayFrom(lowerBound);
return { timeInterval: { from: lowerBound, to: upperBound - 1n } };
}
// FIXME: Refactor dependencies
/**
* @hidden
*/
export const mkGetApplicableActionsDI = (restClient: RestClient): GetApplicableActionsDI => {
return {
getContractContinuation: (contractSourceId: ContractSourceId) => {
// TODO: Add caching
return restClient.getContractSourceById({ contractSourceId });
},
getRuntimeTip: async () => {
const status = await restClient.healthcheck();
return new Date(status.tips.runtimeChain.slotTimeUTC);
},
};
};
type GetApplicableActionsDI = GetContinuationDI & ChainTipDI;
/**
* @hidden
*/
export function getApplicableActions(di: GetApplicableActionsDI) {
return async function (
contractDetails: ContractDetails,
environment?: Environment
): Promise<ApplicableAction[]> {
// If the contract is closed there are no applicable actions
if (contractDetails.type === "closed") return [];
const env = environment ?? (await computeEnvironment(di, contractDetails.currentContract));
const initialReduce = reduceContractUntilQuiescent(
env,
contractDetails.currentState,
contractDetails.currentContract
);
if (initialReduce == "TEAmbiguousTimeIntervalError") throw new Error("AmbiguousTimeIntervalError");
let applicableActions: ApplicableAction[] = initialReduce.reduced
? [
{
actionType: "Advance",
environment: env,
},
]
: [];
const cont = initialReduce.continuation;
if (typeof cont === "object" && "when" in cont) {
const applicableActionsFromCases = cont.when.map((aCase) =>
getApplicableActionFromCase(env, initialReduce.state, aCase)
);
applicableActions = applicableActions.concat(
toApplicableActions(
applicableActionsFromCases.reduce(
mergeApplicableActionAccumulator.concat,
mergeApplicableActionAccumulator.empty
)
)
);
}
return applicableActions;
};
}
/**
* @hidden
*/
export async function mkPartyFilter(wallet: WalletAPI) {
const address = await wallet.getUsedAddresses();
const tokens = await wallet.getTokens();
let tokenMap = new Map<PolicyId, RoleName[]>();
function getTokenMap() {
if (tokenMap.size === 0 && tokens.length > 0) {
tokens.forEach((token) => {
// Role tokens only have 1 element
if (token.quantity > 1) return;
const currentTokens = tokenMap.get(token.assetId.policyId) ?? [];
currentTokens.push(token.assetId.assetName);
tokenMap.set(token.assetId.policyId, currentTokens);
});
}
return tokenMap;
}
return (party: Party, policyId: PolicyId) => {
if ("role_token" in party) {
const policyTokens = getTokenMap().get(policyId);
if (policyTokens === undefined) return false;
return policyTokens.includes(party.role_token);
} else {
return address.includes(party.address as AddressBech32);
}
};
}
/**
* A filter function for applicable actions.
* ```ts
* const applicableActions = await lifecycle.applicableActions.getApplicableActions(contractId);
* const myActionsFilter = await lifecycle.applicableActions.mkFilter(contractDetails);
* const myActions = applicableActions.actions.filter(myActionsFilter);
* ```
* @see How to create the filter using {@link ApplicableActionsAPI.mkFilter | mkFilter}
* @category ApplicableActionsAPI
*/
export type ApplicableActionsFilter = (action: ApplicableAction) => boolean;
/**
* @see How to create the filter using {@link ApplicableActionsAPI.mkFilter | mkFilter}
* @category ApplicableActionsAPI
*/
export type ApplicableActionsWithDetailsFilter = (action: ApplicableAction, contractDetails: ActiveContract) => boolean;
/**
* @hidden
*/
export async function mkApplicableActionsFilter(wallet: WalletAPI) {
const partyFilter = await mkPartyFilter(wallet);
return (action: ApplicableAction, contractDetails: ActiveContract) => {
const applicant = getApplicant(action);
if (applicant === "anybody") return true;
return partyFilter(applicant, contractDetails.roleTokenMintingPolicyId);
};
}
function isDepositAction(action: Action): action is Deposit {
return "party" in action;
}
function isNotify(action: Action): action is Notify {
return "notify_if" in action;
}
function isChoice(action: Action): action is Choice {
return "choose_between" in action;
}
/**
* Internal data structure to be able to accumulate and later flatter applicable actions
* @hidden
*/
type ApplicableActionAccumulator = {
deposits: Record<string, CanDeposit>;
choices: Record<string, CanChoose>;
notifies: CanNotify | undefined;
};
const toApplicableActions = (accumulator: ApplicableActionAccumulator): ApplicableAction[] => {
const deposits = Object.values(accumulator.deposits);
const choices = Object.values(accumulator.choices);
const notifies = accumulator.notifies ? [accumulator.notifies] : [];
return [...deposits, ...choices, ...notifies];
};
const flattenDeposits = {
concat: (fst: CanDeposit, snd: CanDeposit) => {
return fst;
},
};
const partyKey = (party: Party) => {
if ("role_token" in party) {
return `role_${party.role_token}`;
} else {
return `address_${party.address}`;
}
};
const tokenKey = (token: Token) => {
return `${token.currency_symbol}-${token.token_name}`;
};
const accumulatorFromDeposit = (env: Environment, state: MarloweState, action: CanDeposit) => {
const { party, into_account, of_token, deposits } = action.deposit;
const value = evalValue(env, state, deposits);
const depositKey = `${partyKey(party)}-${partyKey(into_account)}-${tokenKey(of_token)}-${value}`;
return {
deposits: { [depositKey]: action },
choices: {},
notifies: undefined,
};
};
const accumulatorFromChoice = (action: CanChoose) => {
const { for_choice } = action.choice;
const choiceKey = `${partyKey(for_choice.choice_owner)}-${for_choice.choice_name}`;
return {
deposits: {},
choices: { [choiceKey]: action },
notifies: undefined,
};
};
const accumulatorFromNotify = (action: CanNotify) => {
return {
deposits: {},
choices: {},
notifies: action,
};
};
function mergeBounds(bounds: Bound[]): Bound[] {
const mergedBounds: Bound[] = [];
const sortedBounds = [...bounds].sort((a, b) => (a.from > b.from ? 1 : a.from < b.from ? -1 : 0));
let currentBound: Bound | null = null;
for (const bound of sortedBounds) {
if (currentBound === null) {
currentBound = { ...bound };
} else {
if (bound.from <= currentBound.to) {
currentBound.to = Big.max(currentBound.to, bound.to);
} else {
mergedBounds.push(currentBound);
currentBound = { ...bound };
}
}
}
if (currentBound !== null) {
mergedBounds.push(currentBound);
}
return mergedBounds;
}
const flattenChoices = {
concat: (fst: CanChoose, snd: CanChoose): CanChoose => {
const mergedBounds = mergeBounds(fst.choice.choose_between.concat(snd.choice.choose_between));
return {
actionType: "Choice",
environment: fst.environment,
choice: {
for_choice: fst.choice.for_choice,
choose_between: mergedBounds,
},
};
},
};
const mergeApplicableActionAccumulator: Monoid<ApplicableActionAccumulator> = {
empty: {
deposits: {},
choices: {},
notifies: undefined,
},
concat: (fst, snd) => {
return {
deposits: R.union(flattenDeposits)(fst.deposits)(snd.deposits),
choices: R.union(flattenChoices)(fst.choices)(snd.choices),
notifies: fst.notifies ?? snd.notifies,
};
},
};
type GetContinuationDI = {
getContractContinuation: (sourceId: ContractSourceId) => Promise<Contract>;
};
function getApplicableActionFromCase(env: Environment, state: MarloweState, aCase: Case): ApplicableActionAccumulator {
if (isDepositAction(aCase.case)) {
const deposit = aCase.case;
return accumulatorFromDeposit(env, state, {
actionType: "Deposit",
deposit,
environment: env,
});
} else if (isChoice(aCase.case)) {
const choice = aCase.case;
return accumulatorFromChoice({
actionType: "Choice",
choice,
environment: env,
});
} else {
const notify = aCase.case;
if (!evalObservation(env, state, notify.notify_if)) {
return mergeApplicableActionAccumulator.empty;
}
return accumulatorFromNotify({
actionType: "Notify",
environment: env,
});
}
}