From 1e6d4aa7925a1b5f633645fb3631270dcf427f5c Mon Sep 17 00:00:00 2001 From: "Rune K. Svendsen" Date: Mon, 8 May 2023 16:09:16 -0400 Subject: [PATCH] Initial commit --- README.md | 10 ++ daml.yaml | 53 ++++++++++ daml/Scripts/Lifecycling.daml | 158 ++++++++++++++++++++++++++++ daml/Scripts/README.md | 5 + daml/Scripts/Settlement.daml | 174 +++++++++++++++++++++++++++++++ daml/Scripts/Transfer.daml | 154 +++++++++++++++++++++++++++ daml/Workflow/CreateAccount.daml | 63 +++++++++++ daml/Workflow/CreditAccount.daml | 40 +++++++ daml/Workflow/DvP.daml | 73 +++++++++++++ daml/Workflow/README.md | 6 ++ daml/Workflow/Transfer.daml | 55 ++++++++++ get-dependencies.bat | 21 ++++ get-dependencies.sh | 28 +++++ 13 files changed, 840 insertions(+) create mode 100644 README.md create mode 100644 daml.yaml create mode 100644 daml/Scripts/Lifecycling.daml create mode 100644 daml/Scripts/README.md create mode 100644 daml/Scripts/Settlement.daml create mode 100644 daml/Scripts/Transfer.daml create mode 100644 daml/Workflow/CreateAccount.daml create mode 100644 daml/Workflow/CreditAccount.daml create mode 100644 daml/Workflow/DvP.daml create mode 100644 daml/Workflow/README.md create mode 100644 daml/Workflow/Transfer.daml create mode 100644 get-dependencies.bat create mode 100755 get-dependencies.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..76f2daf --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Code Samples - Getting Started + +This project contains the source code used in the getting-started tutorials. + +Before opening Visual Studio Code you should run `./get-dependencies.sh` (or `get-dependencies.bat` +for Windows users) to download the required Daml packages. + +You can then open Daml Studio by running `daml studio`, or build the project using `daml build`. + +To run the project, run `daml start` (which also builds the project). diff --git a/daml.yaml b/daml.yaml new file mode 100644 index 0000000..ed34e90 --- /dev/null +++ b/daml.yaml @@ -0,0 +1,53 @@ +# for config file options, refer to +# https://docs.daml.com/tools/assistant.html#project-config-file-daml-yaml + +sdk-version: 2.6.3 +name: tmp-finance-template +source: daml +init-script: Scripts.Transfer:runTransfer +version: 0.0.2 +dependencies: + - daml-prim + - daml-stdlib + - daml-script +data-dependencies: + # INTERFACE DEPENDENCIES + - .lib/daml-finance-interface-account.dar + - .lib/daml-finance-interface-holding.dar + - .lib/daml-finance-interface-instrument-base.dar + - .lib/daml-finance-interface-lifecycle.dar + - .lib/daml-finance-interface-settlement.dar + - .lib/daml-finance-interface-types-common.dar + - .lib/daml-finance-interface-util.dar + # IMPLEMENTATION DEPENDENCIES + - .lib/daml-finance-account.dar + - .lib/daml-finance-holding.dar + - .lib/daml-finance-instrument-token.dar + - .lib/daml-finance-lifecycle.dar + - .lib/daml-finance-settlement.dar + # UNUSED INTERFACES DEPENDENCIES - UNCOMMENT TO ENABLE + # - .lib/daml-finance-interface-claims.dar + # - .lib/daml-finance-interface-data.dar + # - .lib/daml-finance-interface-instrument-bond.dar + # - .lib/daml-finance-interface-instrument-equity.dar + # - .lib/daml-finance-interface-instrument-generic.dar + # - .lib/daml-finance-interface-instrument-option.dar + # - .lib/daml-finance-interface-instrument-swap.dar + # - .lib/daml-finance-interface-instrument-token.dar + # - .lib/daml-finance-interface-types-date.dar + # UNUSED IMPLEMENTATIONS DEPENDENCIES - UNCOMMENT TO ENABLE + # - .lib/daml-finance-claims.dar + # - .lib/daml-finance-data.dar + # - .lib/daml-finance-instrument-bond.dar + # - .lib/daml-finance-instrument-equity.dar + # - .lib/daml-finance-instrument-generic.dar + # - .lib/daml-finance-instrument-option.dar + # - .lib/daml-finance-instrument-swap.dar + # - .lib/daml-finance-util.dar + # UNUSED CONTINGENT CLAIMS DEPENDENCIES - UNCOMMENT TO ENABLE + # - .lib/contingent-claims-core.dar + # - .lib/contingent-claims-lifecycle.dar + # - .lib/contingent-claims-valuation.dar +start-navigator: no +build-options: + - --target=1.15 diff --git a/daml/Scripts/Lifecycling.daml b/daml/Scripts/Lifecycling.daml new file mode 100644 index 0000000..5181236 --- /dev/null +++ b/daml/Scripts/Lifecycling.daml @@ -0,0 +1,158 @@ +module Scripts.Lifecycling where + +import DA.Map qualified as M (empty) +import DA.Set qualified as S (empty, fromList, singleton) +import Daml.Script + +-- INTERFACE DEPENDENCIES -- +import Daml.Finance.Interface.Instrument.Base.Instrument qualified as Instrument (I) +import Daml.Finance.Interface.Lifecycle.Event qualified as Event (I) +import Daml.Finance.Interface.Lifecycle.Rule.Claim qualified as Claim (ClaimEffect(..), I) +import Daml.Finance.Interface.Lifecycle.Rule.Lifecycle qualified as Lifecycle (Evolve(..), I) +import Daml.Finance.Interface.Settlement.Batch qualified as Batch (Settle(..)) +import Daml.Finance.Interface.Settlement.Instruction qualified as Instruction (Allocate(..), Approve(..)) +import Daml.Finance.Interface.Settlement.Types (Allocation(..), Approval(..)) +import Daml.Finance.Interface.Types.Common.Types (Id(..)) +import Daml.Finance.Interface.Util.Common (qty) + +-- IMPLEMENTATION DEPENDENCIES -- +import Daml.Finance.Instrument.Token.Instrument (Instrument(..)) +import Daml.Finance.Lifecycle.Event.Distribution qualified as Distribution (Event(..)) +import Daml.Finance.Lifecycle.Rule.Claim qualified as Claim (Rule(..)) +import Daml.Finance.Lifecycle.Rule.Distribution qualified as Distribution (Rule(..)) + +import Scripts.Settlement (SettlementState(..), runSettlement) + +-- | Test script that +-- 1. executes the `runSettlement` script +-- 2. creates a distribution lifecycle rule +-- 3. creates a distribution lifecycle event +-- 4. lifecycles the distribution event +-- 5. processes the lifecycle effect +-- 6. settles the distribution +runLifecycling : Script() +runLifecycling = do + + -- Execute the `runSettlement` script. Bob now holds 10 tokens in his account. + SettlementState{alice + , bank + , bob + , public + , aliceAccount + , bobAccount + , usdInstrument + , tokenInstrument + , routeProviderCid + , settlementFactoryCid + , aliceHoldingCid + , bobHoldingCid} <- runSettlement + + -- The bank creates a new version of the token instrument (the "ex-distribution" version). This is + -- the version Bob will hold after claiming the effect further down below. + -- NEW_VERSION_BEGIN + let newTokenInstrument = tokenInstrument with version = "1" + now <- getTime + tokenInstrumentCid <- toInterfaceContractId @Instrument.I <$> submit bank do + createCmd Instrument with + depository = bank + issuer = bank + id = tokenInstrument.id + version = "1" + description = "Instrument representing units of a generic token" + validAsOf = now + observers = M.empty + -- NEW_VERSION_END + + -- Create lifecycle rules + -- LIFECYCLE_RULES_BEGIN + distributionRuleCid <- toInterfaceContractId @Lifecycle.I <$> submit bank do + createCmd Distribution.Rule with + providers = S.singleton bank + lifecycler = bank + observers = S.singleton bob + id = Id "Lifecycle rule for distribution" + description = "Rule contract to lifecycle an instrument following a distribution event" + + lifecycleClaimRuleCid <- toInterfaceContractId @Claim.I <$> submitMulti [bank, bob] [] do + createCmd Claim.Rule with + providers = S.fromList [bank, bob] + claimers = S.singleton bob + settlers = S.singleton bob + routeProviderCid + settlementFactoryCid + netInstructions = False + -- LIFECYCLE_RULES_END + + -- Create cash distribution event + -- CREATE_EVENT_BEGIN + distributionEventCid <- toInterfaceContractId @Event.I <$> submit bank do + createCmd Distribution.Event with + providers = S.singleton bank + id = Id "DISTRIBUTION" + description = "Profit distribution" + effectiveTime = now + targetInstrument = tokenInstrument + newInstrument = newTokenInstrument + perUnitDistribution = [qty 0.02 usdInstrument] + observers = S.empty + -- CREATE_EVENT_END + + -- Lifecycle distribution event + -- LIFECYCLE_EVENT_BEGIN + (_, [effectCid]) <- submit bank do + exerciseCmd distributionRuleCid Lifecycle.Evolve with + eventCid = distributionEventCid + observableCids = [] + instrument = tokenInstrument + -- LIFECYCLE_EVENT_END + + -- Claim effect + -- CLAIM_EVENT_BEGIN + result <- submitMulti [bob] [public] do + exerciseCmd lifecycleClaimRuleCid Claim.ClaimEffect with + claimer = bob + holdingCids = [bobHoldingCid] + effectCid + batchId = Id "DistributionSettlement" + let [bobInstructionCid, bankInstructionCid, couponInstructionCid] = result.instructionCids + -- CLAIM_EVENT_END + + -- EFFECT_SETTLEMENT_BEGIN + -- Allocate instruction + (bobInstructionCid, _) <- submit bob do + exerciseCmd bobInstructionCid Instruction.Allocate with + actors = S.singleton bob + allocation = Pledge bobHoldingCid + + (bankInstructionCid, _) <- submit bank do + exerciseCmd bankInstructionCid Instruction.Allocate with + actors = S.singleton bank + allocation = CreditReceiver + + (couponInstructionCid, _) <- submit bank do + exerciseCmd couponInstructionCid Instruction.Allocate with + actors = S.singleton bank + allocation = CreditReceiver + + -- Approve instruction + bobInstructionCid <- submit bank do + exerciseCmd bobInstructionCid Instruction.Approve with + actors = S.singleton bank + approval = DebitSender + + bankInstructionCid <- submit bob do + exerciseCmd bankInstructionCid Instruction.Approve with + actors = S.singleton bob + approval = TakeDelivery bobAccount + + couponInstructionCid <- submit bob do + exerciseCmd couponInstructionCid Instruction.Approve with + actors = S.singleton bob + approval = TakeDelivery bobAccount + + -- Settle batch + submitMulti [bob] [public] do + exerciseCmd result.batchCid Batch.Settle with actors = S.singleton bob + -- EFFECT_SETTLEMENT_END + + pure () diff --git a/daml/Scripts/README.md b/daml/Scripts/README.md new file mode 100644 index 0000000..abf27e5 --- /dev/null +++ b/daml/Scripts/README.md @@ -0,0 +1,5 @@ +# Setup scripts + +This folder includes setup scripts that are executed on a one-off basis. + +Files in this folder can reference both interface and implementation packages of `daml-finance`. diff --git a/daml/Scripts/Settlement.daml b/daml/Scripts/Settlement.daml new file mode 100644 index 0000000..ef311c4 --- /dev/null +++ b/daml/Scripts/Settlement.daml @@ -0,0 +1,174 @@ +module Scripts.Settlement where + +import DA.Map as M (empty) +import DA.Set as S (fromList, singleton) +import Daml.Script + +-- INTERFACE DEPENDENCIES -- +import Daml.Finance.Interface.Holding.Base qualified as Holding (I) +import Daml.Finance.Interface.Instrument.Base.Instrument qualified as Instrument (I) +import Daml.Finance.Interface.Settlement.Batch qualified as Batch (Settle(..)) +import Daml.Finance.Interface.Settlement.Factory qualified as Settlement (F) +import Daml.Finance.Interface.Settlement.Instruction qualified as Instruction (Allocate(..), Approve(..)) +import Daml.Finance.Interface.Settlement.RouteProvider qualified as RouteProvider (I) +import Daml.Finance.Interface.Settlement.Types (Allocation(..), Approval(..)) +import Daml.Finance.Interface.Types.Common.Types (AccountKey, Id(..), InstrumentKey(..)) +import Daml.Finance.Interface.Util.Common (qty) + +-- IMPLEMENTATION DEPENDENCIES -- +import Daml.Finance.Instrument.Token.Instrument (Instrument(..)) +import Daml.Finance.Settlement.Factory (Factory(..)) +import Daml.Finance.Settlement.RouteProvider.SingleCustodian (SingleCustodian(..)) + +import Workflow.CreditAccount qualified as CreditAccount +import Workflow.DvP qualified as DvP + +import Scripts.Transfer (TransferState(..), runTransfer) + +-- | Helper container used to transfer state from one script to another. +data SettlementState = SettlementState + with + alice : Party + bank : Party + bob : Party + public : Party + aliceAccount : AccountKey + bobAccount : AccountKey + usdInstrument : InstrumentKey + tokenInstrument : InstrumentKey + routeProviderCid : ContractId RouteProvider.I + settlementFactoryCid : ContractId Settlement.F + aliceHoldingCid : ContractId Holding.I + bobHoldingCid : ContractId Holding.I + deriving (Eq, Show) + +-- | Test script that +-- 1. executes the `runTransfer` script +-- 2. creates a token instrument +-- 3. credits a token holding to Alice in her bank account +-- 4. atomically exchanges the token against the cash holding +runSettlement : Script SettlementState +runSettlement = do + + -- Execute the `runTransfer` script. Bob now holds USD 1000 in his account. + TransferState{alice + , bank + , bob + , public + , aliceAccount + , bobAccount + , cashInstrument = usdInstrument + , holdingFactoryCid + , newHoldingCid = bobHoldingCid} <- runTransfer + + -- Bank creates a token instrument + let + instrumentId = Id "TOKEN" + instrumentVersion = "0" + tokenInstrument = InstrumentKey with + issuer = bank + depository = bank + id = instrumentId + version = instrumentVersion + + now <- getTime + + tokenInstrumentCid <- toInterfaceContractId @Instrument.I <$> submit bank do + createCmd Instrument with + depository = bank + issuer = bank + id = instrumentId + version = instrumentVersion + description = "Instrument representing units of a generic token" + validAsOf = now + observers = empty + + -- Credit Alice's account with a token holding + aliceRequestCid <- submit alice do + createCmd CreditAccount.Request with + account = aliceAccount + instrument = tokenInstrument + amount = 10.0 + aliceHoldingCid <- submit bank do exerciseCmd aliceRequestCid CreditAccount.Accept + + -- Setup a route provider + -- This is used transform settlement `Step`s into a `RoutedStep`s using a single custodian + -- ROUTE_PROVIDER_BEGIN + routeProviderCid <- toInterfaceContractId @RouteProvider.I <$> submit bank do + createCmd SingleCustodian with + provider = bank; observers = S.fromList [alice, bob] ; custodian = bank + -- ROUTE_PROVIDER_END + + -- Setup a Settlement Factory facility + -- This is used to generate settlement instructions from a list of `RoutedStep`s + -- SETTLEMENT_FACTORY_BEGIN + settlementFactoryCid <- toInterfaceContractId @Settlement.F <$> submit bank do + createCmd Factory with + provider = bank + observers = S.fromList [alice, bob] + -- SETTLEMENT_FACTORY_END + + -- Alice proposes an FX trade to Bob + -- DVP_PROPOSE_BEGIN + dvpProposalCid <- submit bob do + createCmd DvP.Proposal with + id = "xccy trade" + recQuantity = qty 10.0 tokenInstrument + payQuantity = qty 1000.0 usdInstrument + proposer = bob + counterparty = alice + routeProviderCid + settlementFactoryCid + -- DVP_PROPOSE_END + + -- DVP_ACCEPT_BEGIN + (batchCid, recSettleInstructionCid, paySettleInstructionCid) <- submit alice do + exerciseCmd dvpProposalCid DvP.Accept + -- DVP_ACCEPT_END + + -- Settle the DvP Trade + + -- i. Bob allocates his asset, Alice approves by providing her account. + (allocatedPaySettleInstructionCid, _) <- submit bob do + exerciseCmd paySettleInstructionCid Instruction.Allocate with + actors = S.singleton bob + allocation = Pledge bobHoldingCid + + approvedPaySettleInstructionCid <- submit alice do + exerciseCmd allocatedPaySettleInstructionCid Instruction.Approve with + actors = S.singleton alice + approval = TakeDelivery aliceAccount + + -- ii. Alice allocates her asset, Bob approves by providing his account. + -- ALLOCATE_APPROVE_BEGIN + (allocatedRecSettleInstructionCid, _) <- submit alice do + exerciseCmd recSettleInstructionCid Instruction.Allocate with + actors = S.singleton alice + allocation = Pledge aliceHoldingCid + + approvedRecSettleInstructionCid <- submit bob do + exerciseCmd allocatedRecSettleInstructionCid Instruction.Approve with + actors = S.singleton bob + approval = TakeDelivery bobAccount + -- ALLOCATE_APPROVE_END + + -- iii. Bob executes the settlement. + -- SETTLE_BEGIN + [bobHoldingCid, aliceHoldingCid] <- submitMulti [bob] [public] do + exerciseCmd batchCid Batch.Settle with + actors = singleton bob + -- SETTLE_END + + pure $ SettlementState with + alice + bank + bob + public + aliceAccount + bobAccount + usdInstrument + tokenInstrument + routeProviderCid + settlementFactoryCid + aliceHoldingCid = toInterfaceContractId aliceHoldingCid + bobHoldingCid = toInterfaceContractId bobHoldingCid diff --git a/daml/Scripts/Transfer.daml b/daml/Scripts/Transfer.daml new file mode 100644 index 0000000..4a2f9e4 --- /dev/null +++ b/daml/Scripts/Transfer.daml @@ -0,0 +1,154 @@ +module Scripts.Transfer where + +import DA.Map (empty, fromList) +import DA.Set (singleton) +import Daml.Script + +-- INTERFACE DEPENDENCIES -- +import Daml.Finance.Interface.Account.Factory qualified as Account (F) +import Daml.Finance.Interface.Holding.Base qualified as Holding (I) +import Daml.Finance.Interface.Holding.Factory qualified as Holding (F) +import Daml.Finance.Interface.Instrument.Base.Instrument qualified as Instrument (I) +import Daml.Finance.Interface.Types.Common.Types (AccountKey, Id(..), InstrumentKey(..)) + +-- IMPLEMENTATION DEPENDENCIES -- +import Daml.Finance.Account.Account qualified as Account (Factory(..)) +import Daml.Finance.Holding.Fungible qualified as Fungible (Factory(..)) +import Daml.Finance.Instrument.Token.Instrument (Instrument(..)) + +import Workflow.CreateAccount qualified as CreateAccount +import Workflow.CreditAccount qualified as CreditAccount +import Workflow.Transfer qualified as Transfer + +-- | Helper container used to transfer state from one script to another. +data TransferState = TransferState + with + alice : Party + bank : Party + bob : Party + public : Party + aliceAccount : AccountKey + bobAccount : AccountKey + cashInstrument : InstrumentKey + holdingFactoryCid : ContractId Holding.F + newHoldingCid : ContractId Holding.I + deriving (Eq, Show) + +-- | Test script that +-- 1. creates an account for Alice and Bob at the Bank +-- 2. issues a cash instrument +-- 3. credits a cash holding to Alice in her bank account +-- 4. transfers the holding from Alice to Bob +runTransfer : Script TransferState +runTransfer = do + + -- Allocate parties + [alice, bank, bob, public] <- mapA createParty $ ["Alice", "Bank", "Bob", "Public"] + + -- Account Factory (it is used by the bank to create accounts) + -- CREATE_ACCOUNT_FACTORY_BEGIN + accountFactoryCid <- toInterfaceContractId @Account.F <$> submit bank do + createCmd Account.Factory with provider = bank; observers = empty + -- CREATE_ACCOUNT_FACTORY_END + + -- Holding Factory (it is used by the bank to create holdings with the desired implementation) + -- CREATE_HOLDING_FACTORY_BEGIN + holdingFactoryCid <- toInterfaceContractId @Holding.F <$> submit bank do + createCmd Fungible.Factory with + provider = bank + observers = fromList [("PublicObserver", singleton public )] + -- CREATE_HOLDING_FACTORY_END + + -- Alice and Bob setup account @Bank + -- SETUP_ALICE_ACCOUNT_BEGIN + aliceRequestCid <- submit alice do + createCmd CreateAccount.Request with owner = alice; custodian = bank + aliceAccount <- submit bank do + exerciseCmd aliceRequestCid CreateAccount.Accept with + label = "Alice@Bank" + description = "Account of Alice at Bank" + accountFactoryCid = accountFactoryCid + holdingFactoryCid = holdingFactoryCid + observers = [] + -- SETUP_ALICE_ACCOUNT_END + + bobRequestCid <- submit bob do createCmd CreateAccount.Request with owner = bob; custodian = bank + bobAccount <- submit bank do + exerciseCmd bobRequestCid CreateAccount.Accept with + label = "Bob@Bank" + description = "Account of Bob at Bank" + accountFactoryCid = accountFactoryCid + holdingFactoryCid = holdingFactoryCid + observers = [alice] + + -- Bank creates the cash instrument + -- ISSUE_CASH_INSTRUMENT_BEGIN + let + instrumentId = Id "USD" + instrumentVersion = "0" + now <- getTime + + cashInstrumentCid <- toInterfaceContractId @Instrument.I <$> submit bank do + createCmd Instrument with + depository = bank + issuer = bank + id = instrumentId + version = instrumentVersion + description = "Instrument representing units of USD" + validAsOf = now + observers = empty + -- ISSUE_CASH_INSTRUMENT_END + + -- Alice deposits cash at the bank + -- CREATE_ALICE_HOLDING_BEGIN + aliceRequestCid <- submit alice do + createCmd CreditAccount.Request with + account = aliceAccount + instrument = InstrumentKey with + issuer = bank + depository = bank + id = instrumentId + version = instrumentVersion + amount = 1000.0 + + aliceCashHoldingCid <- submit bank do exerciseCmd aliceRequestCid CreditAccount.Accept + -- CREATE_ALICE_HOLDING_END + + -- Bob requests a cash transfer from Alice + -- TRANSFER_BEGIN + let + cashInstrument = InstrumentKey with + issuer = bank + depository = bank + id = instrumentId + version = instrumentVersion + + transferRequestCid <- submit bob do + createCmd Transfer.Request with + receiverAccount = bobAccount + instrument = cashInstrument + amount = 1000.0 + currentOwner = alice + + newHoldingCid <- submitMulti [alice] [public] do + exerciseCmd transferRequestCid Transfer.Accept with holdingCid = aliceCashHoldingCid + -- TRANSFER_END + + pure $ TransferState with + alice + bank + bob + public + aliceAccount + bobAccount + cashInstrument + holdingFactoryCid + newHoldingCid + +-- | Creates a user + party given a hint +createParty : Text -> Script Party +createParty name = do + party <- allocatePartyWithHint name $ PartyIdHint name + userId <- validateUserId name + createUser (User userId (Some party)) [CanActAs party] + pure party diff --git a/daml/Workflow/CreateAccount.daml b/daml/Workflow/CreateAccount.daml new file mode 100644 index 0000000..956eeb9 --- /dev/null +++ b/daml/Workflow/CreateAccount.daml @@ -0,0 +1,63 @@ +module Workflow.CreateAccount where + +import DA.Map qualified as M (fromList) +import DA.Set qualified as S (fromList, singleton) +import Daml.Finance.Interface.Account.Account qualified as Account (Controllers(..)) +import Daml.Finance.Interface.Account.Factory qualified as Account (Create(..), F) +import Daml.Finance.Interface.Holding.Factory qualified as Holding (F) +import Daml.Finance.Interface.Types.Common.Types (AccountKey(..), Id(..)) + +-- | Initiate / Accept template to open an account. +-- The account is created using an `Account.Factory` template. By doing so, our workflow is generic +-- and does not depend on the specific account implementation. For the same reason, we need to +-- provide a `Holding.Factory` that will be used by the account to create holdings without depending +-- on the specific implementation. +template Request + with + custodian : Party + -- ^ The account's custodian. + owner : Party + -- ^ The account's owner. + where + signatory owner + observer custodian + + choice Accept : AccountKey + -- ^ Accept the request. + with + label : Text + -- ^ A textual label. + description : Text + -- ^ An extended textual description. + accountFactoryCid : ContractId Account.F + -- ^ The account factory. This is used to create the account template. + holdingFactoryCid : ContractId Holding.F + -- ^ The holding factory. This is used within an account to create holdings. + observers : [Party] + -- ^ Observers of the account to be created. + controller custodian + do + let + observersSet = S.fromList observers + accountKey = AccountKey with custodian = custodian, owner = owner, id = Id label + + accountCid <- exercise accountFactoryCid Account.Create with + account = accountKey + description = description + holdingFactoryCid = holdingFactoryCid + controllers = Account.Controllers with + outgoing = S.singleton owner + incoming = S.singleton owner + observers = M.fromList [("AccountObservers", observersSet)] + + pure accountKey + + choice Decline : () + -- ^ Decline the request. + controller custodian + do pure () + + choice Withdraw : () + -- ^ Withdraw the request. + controller owner + do pure () diff --git a/daml/Workflow/CreditAccount.daml b/daml/Workflow/CreditAccount.daml new file mode 100644 index 0000000..f668c8e --- /dev/null +++ b/daml/Workflow/CreditAccount.daml @@ -0,0 +1,40 @@ +module Workflow.CreditAccount where + +import Daml.Finance.Interface.Account.Account qualified as Account (Credit(..), I, exerciseInterfaceByKey) +import Daml.Finance.Interface.Holding.Base qualified as Holding (I) +import Daml.Finance.Interface.Types.Common.Types (AccountKey(..), InstrumentKey) +import Daml.Finance.Interface.Util.Common (qty) + +-- | Initiate / Accept template to credit a holding to an existing account. +template Request + with + account : AccountKey + -- ^ The account receiving the holding. + instrument : InstrumentKey + -- ^ The instrument to be credited. + amount : Decimal + -- ^ The number of units of the specified instrument to be credited. + where + signatory account.owner + observer account.custodian + + ensure amount > 0.0 + + choice Accept : ContractId Holding.I + -- ^ Accept the request. In the case of physical assets (e.g. paper certificates, banknotes), + -- a custodian would generally accept the request once they have got hold of the physical + -- asset. + controller account.custodian + do + Account.exerciseInterfaceByKey @Account.I account account.custodian Account.Credit with + quantity = qty amount instrument + + choice Decline : () + -- ^ Decline the request. + controller account.custodian + do pure () + + choice Withdraw : () + -- ^ Withdraw the request. + controller account.owner + do pure () diff --git a/daml/Workflow/DvP.daml b/daml/Workflow/DvP.daml new file mode 100644 index 0000000..418fbfa --- /dev/null +++ b/daml/Workflow/DvP.daml @@ -0,0 +1,73 @@ +module Workflow.DvP where + +import DA.Set (fromList, singleton) +import Daml.Finance.Interface.Settlement.Batch qualified as Batch (I) +import Daml.Finance.Interface.Settlement.Factory qualified as Settlement (F, Instruct(..)) +import Daml.Finance.Interface.Settlement.Instruction qualified as Instruction (I) +import Daml.Finance.Interface.Settlement.RouteProvider qualified as RouteProvider (I, Discover(..)) +import Daml.Finance.Interface.Settlement.Types (Step(..)) +import Daml.Finance.Interface.Types.Common.Types (Id(..), InstrumentQuantity) + +-- | Initiate / Accept template to exchange two holdings. +template Proposal + with + recQuantity : InstrumentQuantity + -- ^ The receiving leg (instrument and amount). + payQuantity : InstrumentQuantity + -- ^ The pay leg (instrument and amount). + proposer : Party + -- ^ The party proposing the trade. They receive the receiving leg in exchange for the pay + -- leg. + counterparty : Party + -- ^ The trade counterparty. They receive the pay leg in exchange for the receiving leg. + routeProviderCid : ContractId RouteProvider.I + -- ^ The route provider to discover settlement paths. + settlementFactoryCid : ContractId Settlement.F + -- ^ The factory contract for the settlement batch. + id : Text + -- ^ A textual identifier. + where + signatory proposer + observer counterparty + + ensure recQuantity.amount > 0.0 && payQuantity.amount > 0.0 + + choice Accept : (ContractId Batch.I, ContractId Instruction.I, ContractId Instruction.I) + controller counterparty + do + let + -- Settlement of REC leg + recStep = Step with sender = counterparty; receiver = proposer; quantity = recQuantity + + -- Settlement of PAY leg + payStep = Step with sender = proposer; receiver = counterparty; quantity = payQuantity + + -- Discover settlement routes for the steps + routedSteps <- exercise routeProviderCid RouteProvider.Discover with + discoverors = fromList [proposer, counterparty] + contextId = None + steps = [recStep, payStep] + + -- INSTRUCT_BEGIN + (containerCid, [recInstructionCid, payInstructionCid]) <- + exercise settlementFactoryCid Settlement.Instruct with + instructors = fromList [proposer, counterparty] + settlers = singleton proposer + id = Id id + description = "Settlement for " <> id + contextId = None + routedSteps + settlementTime = None -- i.e., immediate settlement + -- INSTRUCT_END + + pure (containerCid, recInstructionCid, payInstructionCid) + + choice Decline : () + -- ^ Decline the request. + controller counterparty + do pure () + + choice Withdraw : () + -- ^ Withdraw the request. + controller proposer + do pure () diff --git a/daml/Workflow/README.md b/daml/Workflow/README.md new file mode 100644 index 0000000..288b88a --- /dev/null +++ b/daml/Workflow/README.md @@ -0,0 +1,6 @@ +# Workflows + +This folder includes user-defined workflows which encapsulate the core business logic of the +application. + +Files in this folder can reference only the interface packages of `daml-finance`. diff --git a/daml/Workflow/Transfer.daml b/daml/Workflow/Transfer.daml new file mode 100644 index 0000000..5ea4f76 --- /dev/null +++ b/daml/Workflow/Transfer.daml @@ -0,0 +1,55 @@ +module Workflow.Transfer where + +import DA.Assert ((===)) +import DA.Set (fromList) +import Daml.Finance.Interface.Holding.Base qualified as Holding (I) +import Daml.Finance.Interface.Holding.Transferable qualified as Transferable (I, Transfer(..)) +import Daml.Finance.Interface.Holding.Util (getAmount, getInstrument) +import Daml.Finance.Interface.Types.Common.Types (AccountKey(..), InstrumentKey) + +-- | Initiate / Accept template to transfer a holding to a new owner. +template Request + with + receiverAccount : AccountKey + -- ^ The account where the holding is sent. + instrument : InstrumentKey + -- ^ The instrument referenced by the holding to be transferred. + amount : Decimal + -- ^ Number of units to be transferred. + currentOwner : Party + -- ^ The owner of the holding to be transferred. + where + signatory receiverAccount.owner + observer currentOwner + + ensure amount > 0.0 + + choice Accept : ContractId Holding.I + with + holdingCid : ContractId Holding.I + controller currentOwner + do + -- Sanity checks + holding <- fetch holdingCid + getAmount holding === amount + getInstrument holding === instrument + + -- DO_TRANSFER_BEGIN + let transferableCid = coerceInterfaceContractId @Transferable.I holdingCid + + newTransferableCid <- exercise transferableCid Transferable.Transfer with + actors = fromList [currentOwner, receiverAccount.owner] + newOwnerAccount = receiverAccount + + pure $ toInterfaceContractId @Holding.I newTransferableCid + -- DO_TRANSFER_END + + choice Decline : () + -- ^ Decline the request. + controller currentOwner + do pure () + + choice Withdraw : () + -- ^ Withdraw the request. + controller receiverAccount.owner + do pure () diff --git a/get-dependencies.bat b/get-dependencies.bat new file mode 100644 index 0000000..a454756 --- /dev/null +++ b/get-dependencies.bat @@ -0,0 +1,21 @@ +:: Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +:: SPDX-License-Identifier: Apache-2.0 + +@echo off + +:: Create .lib directory +if exist ".\.lib\" rmdir /s /q .lib +mkdir .\.lib + +:: Get the dependency list +echo Downloading the list of dependencies +for /f "tokens=2" %%a IN ('findstr "^version" daml.yaml') DO (set version=%%a) + +curl -Lf# "https://raw.githubusercontent.com/digital-asset/daml-finance/main/docs/code-samples/getting-started-config/%version%.conf" -o .lib/%version%.conf + +for /F "tokens=*" %%a in (.lib/%version%.conf) do ( + for /F "tokens=1,2" %%b in ("%%a") do ( + echo Downloading: %%b to %%c + curl -Lf# "%%b" -o %%c + ) +) diff --git a/get-dependencies.sh b/get-dependencies.sh new file mode 100755 index 0000000..5209cec --- /dev/null +++ b/get-dependencies.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +# Create .lib directory +if [[ -d .lib ]]; then + rm -r .lib +fi +mkdir .lib + +# Get the dependency list +echo "Downloading the list of dependencies" +version=$(grep '^version' daml.yaml | cut -d " " -f 2) +curl -L# \ + -H 'Cache-Control: no-cache, no-store' \ + -o .lib/${version}.conf \ + https://raw.githubusercontent.com/digital-asset/daml-finance/main/docs/code-samples/getting-started-config/${version}.conf + +# For each dependency, download and install +while IFS=" " read -r url out +do + printf "Downloading: %s, to: %s\n" "$url" "$out" + curl -Lf# "${url}" -o ${out} +done < .lib/${version}.conf + +echo "All dependencies successfully downloaded!"