create-canton-app 1.4.2 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ {
2
+ "files.readonlyInclude": {
3
+ "**/.daml/unpacked-dars/**": true
4
+ }
5
+ }
package/README.md CHANGED
@@ -60,9 +60,9 @@ Edit `src/commands/create.js` line 43:
60
60
 
61
61
  ```javascript
62
62
  choices: [
63
- { name: 'Token Contract', value: 'token' },
63
+ { name: 'Bond Trading', value: 'bondtrading' },
64
64
 
65
- { name: 'Escrow Contract', value: 'escrow' },
65
+ { name: 'Collateral Master', value: 'collateral' },
66
66
 
67
67
  { name: 'My Template', value: 'my-template' }, // ADD THIS
68
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-canton-app",
3
- "version": "1.4.2",
3
+ "version": "1.5.2",
4
4
  "description": "Scaffold Canton Network/Daml projects in seconds",
5
5
  "main": "bin/create-canton-app.js",
6
6
  "repository": {
@@ -139,9 +139,9 @@ async function create(projectName, options) {
139
139
  name: 'template',
140
140
  message: 'Which template would you like to use?',
141
141
  choices: [
142
- { name: 'Token Contract (fungible token like ERC20)', value: 'token' },
143
- { name: 'Escrow Contract (multi-party escrow)', value: 'escrow' },
144
- { name: 'Empty Template (blank starter)', value: 'empty' }
142
+ { name: 'Token Transfer System Template', value: 'TokenTransfer' },
143
+ { name: 'Multi-Party Agreement System Template', value: 'Multiparty' },
144
+ { name: 'Asset Holding System Template', value: 'AssetOwner' }
145
145
  ]
146
146
  }
147
147
  ]);
@@ -0,0 +1,276 @@
1
+ -- | Asset Holding System for Canton Network
2
+ -- CIP-0056 Token Standard Compatible
3
+ -- UTXO-style holdings with memo support for exchange deposits
4
+ module AssetHolding where
5
+
6
+ import DA.TextMap (TextMap)
7
+ import qualified DA.TextMap as TM
8
+ import Daml.Script
9
+
10
+ -------------------------------------------------------------------------------
11
+ -- DATA TYPES
12
+ -------------------------------------------------------------------------------
13
+
14
+ data InstrumentId = InstrumentId
15
+ with
16
+ admin : Party
17
+ id : Text
18
+ deriving (Eq, Show)
19
+
20
+ data AssetLock = AssetLock
21
+ with
22
+ lockHolder : Party
23
+ lockReason : Text
24
+ lockedUntil : Optional Time
25
+ deriving (Eq, Show)
26
+
27
+ data Metadata = Metadata
28
+ with
29
+ values : TextMap Text
30
+ deriving (Eq, Show)
31
+
32
+ -------------------------------------------------------------------------------
33
+ -- METADATA HELPERS
34
+ -------------------------------------------------------------------------------
35
+
36
+ createMemo : Text -> Metadata
37
+ createMemo memo = Metadata with
38
+ values = TM.fromList [("splice.lfdecentralizedtrust.org/reason", memo)]
39
+
40
+ getMemo : Metadata -> Optional Text
41
+ getMemo (Metadata values) = TM.lookup "splice.lfdecentralizedtrust.org/reason" values
42
+
43
+ emptyMetadata : Metadata
44
+ emptyMetadata = Metadata with values = TM.empty
45
+
46
+ -------------------------------------------------------------------------------
47
+ -- MAIN ASSET CONTRACT
48
+ -------------------------------------------------------------------------------
49
+
50
+ template Asset
51
+ with
52
+ issuer : Party
53
+ owner : Party
54
+ amount : Decimal
55
+ symbol : Text
56
+ instrumentId : InstrumentId
57
+ lock : Optional AssetLock
58
+ meta : Metadata
59
+ where
60
+ signatory issuer, owner
61
+ observer (case lock of
62
+ Some l -> [l.lockHolder]
63
+ None -> [])
64
+
65
+ ensure
66
+ amount > 0.0 &&
67
+ instrumentId.admin == issuer
68
+
69
+ choice Transfer : ContractId Asset
70
+ with
71
+ newOwner : Party
72
+ transferMeta : Metadata
73
+ controller owner
74
+ do
75
+ assertMsg "Cannot transfer locked asset" (isNone lock)
76
+ assertMsg "Cannot transfer to self" (newOwner /= owner)
77
+ create this with
78
+ owner = newOwner
79
+ meta = transferMeta
80
+
81
+ choice ProposeTransfer : ContractId TransferOffer
82
+ with
83
+ newOwner : Party
84
+ offerMeta : Metadata
85
+ expiresAt : Time
86
+ controller owner
87
+ do
88
+ assertMsg "Cannot propose transfer of locked asset" (isNone lock)
89
+ now <- getTime
90
+ assertMsg "Expiry must be in future" (expiresAt > now)
91
+
92
+ create TransferOffer with
93
+ assetCid = self
94
+ asset = this
95
+ sender = owner
96
+ receiver = newOwner
97
+ offerMetadata = offerMeta
98
+ createdAt = now
99
+ expiresAt = expiresAt
100
+
101
+ choice Split : (ContractId Asset, ContractId Asset)
102
+ with
103
+ splitAmount : Decimal
104
+ controller owner
105
+ do
106
+ assertMsg "Split amount must be positive" (splitAmount > 0.0)
107
+ assertMsg "Insufficient amount for split" (amount > splitAmount)
108
+ assertMsg "Cannot split locked asset" (isNone lock)
109
+
110
+ asset1 <- create this with
111
+ amount = splitAmount
112
+ meta = createMemo "Split from parent holding"
113
+
114
+ asset2 <- create this with
115
+ amount = amount - splitAmount
116
+ meta = createMemo "Split from parent holding"
117
+
118
+ return (asset1, asset2)
119
+
120
+ choice Merge : ContractId Asset
121
+ with
122
+ otherAssetCid : ContractId Asset
123
+ controller owner
124
+ do
125
+ other <- fetch otherAssetCid
126
+
127
+ assertMsg "Issuers must match" (other.issuer == issuer)
128
+ assertMsg "Symbols must match" (other.symbol == symbol)
129
+ assertMsg "Owners must match" (other.owner == owner)
130
+ assertMsg "InstrumentIds must match" (other.instrumentId == instrumentId)
131
+ assertMsg "Cannot merge locked assets" (isNone lock && isNone other.lock)
132
+
133
+ exercise otherAssetCid Archive
134
+
135
+ create this with
136
+ amount = amount + other.amount
137
+ meta = createMemo "Merged from multiple holdings"
138
+
139
+ choice LockAsset : ContractId Asset
140
+ with
141
+ lockData : AssetLock
142
+ controller owner
143
+ do
144
+ assertMsg "Asset is already locked" (isNone lock)
145
+ create this with lock = Some lockData
146
+
147
+ choice UnlockAsset : ContractId Asset
148
+ with
149
+ unlockTime : Time
150
+ lockHolder : Party
151
+ controller lockHolder
152
+ do
153
+ assertMsg "Asset is not locked" (isSome lock)
154
+
155
+ case lock of
156
+ Some l -> do
157
+ assertMsg "Not the lock holder" (l.lockHolder == lockHolder)
158
+ case l.lockedUntil of
159
+ Some until -> assertMsg "Lock period not expired" (unlockTime >= until)
160
+ None -> return ()
161
+ None -> abort "Asset is not locked"
162
+
163
+ create this with lock = None
164
+
165
+ choice Burn : ()
166
+ controller owner
167
+ do
168
+ assertMsg "Cannot burn locked asset" (isNone lock)
169
+ return ()
170
+
171
+ template TransferOffer
172
+ with
173
+ assetCid : ContractId Asset
174
+ asset : Asset
175
+ sender : Party
176
+ receiver : Party
177
+ offerMetadata : Metadata
178
+ createdAt : Time
179
+ expiresAt : Time
180
+ where
181
+ signatory sender
182
+ observer receiver
183
+
184
+ choice AcceptTransfer : ContractId Asset
185
+ controller receiver
186
+ do
187
+ now <- getTime
188
+ assertMsg "Transfer offer has expired" (now <= expiresAt)
189
+
190
+ exercise assetCid Archive
191
+
192
+ create asset with
193
+ owner = receiver
194
+ meta = offerMetadata
195
+
196
+ choice RejectTransfer : ContractId Asset
197
+ with
198
+ rejectionReason : Text
199
+ controller receiver
200
+ do
201
+ return assetCid
202
+
203
+ choice WithdrawOffer : ContractId Asset
204
+ controller sender
205
+ do
206
+ return assetCid
207
+
208
+ -------------------------------------------------------------------------------
209
+ -- EXCHANGE DEPOSIT TEMPLATE
210
+ -------------------------------------------------------------------------------
211
+
212
+ template ExchangeDepositAddress
213
+ with
214
+ exchange : Party
215
+ exchangeVault : Party
216
+ depositMemo : Text
217
+ supportedInstruments : [InstrumentId]
218
+ where
219
+ signatory exchange
220
+ observer exchangeVault
221
+
222
+ nonconsuming choice DepositToExchange : ContractId Asset
223
+ with
224
+ userAssetCid : ContractId Asset
225
+ user : Party
226
+ controller user, exchange
227
+ do
228
+ asset <- fetch userAssetCid
229
+
230
+ assertMsg "Instrument not supported"
231
+ (asset.instrumentId `elem` supportedInstruments)
232
+
233
+ exercise userAssetCid Transfer with
234
+ newOwner = exchangeVault
235
+ transferMeta = createMemo depositMemo
236
+
237
+ -------------------------------------------------------------------------------
238
+ -- HELPER FUNCTIONS
239
+ -------------------------------------------------------------------------------
240
+
241
+ isNone : Optional a -> Bool
242
+ isNone opt = case opt of
243
+ None -> True
244
+ Some _ -> False
245
+
246
+ isSome : Optional a -> Bool
247
+ isSome opt = not (isNone opt)
248
+
249
+ -------------------------------------------------------------------------------
250
+ -- TEST SCRIPTS
251
+ -------------------------------------------------------------------------------
252
+
253
+ setup : Script ()
254
+ setup = do
255
+ issuer <- allocateParty "AssetIssuer"
256
+ alice <- allocateParty "Alice"
257
+ bob <- allocateParty "Bob"
258
+
259
+ let instrumentId = InstrumentId with
260
+ admin = issuer
261
+ id = "GOLD-TOKEN"
262
+
263
+ assetCid <- submitMulti [issuer, alice] [] $ createCmd Asset with
264
+ issuer = issuer
265
+ owner = alice
266
+ amount = 1000.0
267
+ symbol = "GOLD"
268
+ instrumentId = instrumentId
269
+ lock = None
270
+ meta = createMemo "Initial issuance"
271
+
272
+ submitMulti [alice, issuer] [] $ exerciseCmd assetCid Transfer with
273
+ newOwner = bob
274
+ transferMeta = createMemo "Payment to Bob"
275
+
276
+ return ()
@@ -0,0 +1,18 @@
1
+ # Asset Ownership System for Canton Network
2
+ # CIP-0056 Token Standard Compatible
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ sdk-version: 3.4.9
6
+ name: AssetOwnership
7
+ source: daml/AssetHolding.daml
8
+ parties:
9
+ - AssetIssuer
10
+ - Alice
11
+ - Bob
12
+ - Exchange
13
+ - ExchangeVault
14
+ version: 1.0.0
15
+ dependencies:
16
+ - daml-prim
17
+ - daml-stdlib
18
+ - daml-script
@@ -0,0 +1,256 @@
1
+ -- | Multi-Party Agreement System for Canton Network
2
+ -- Supports incremental signing, governance, multi-sig workflows
3
+ module MultiPartyAgreement where
4
+
5
+ import DA.List (delete, sort)
6
+ import Daml.Script
7
+
8
+ -------------------------------------------------------------------------------
9
+ -- DATA TYPES
10
+ -------------------------------------------------------------------------------
11
+
12
+ data AgreementType
13
+ = Governance
14
+ | Transaction
15
+ | Legal
16
+ | Policy
17
+ | Custom Text
18
+ deriving (Eq, Show)
19
+
20
+ data AgreementMetadata = AgreementMetadata
21
+ with
22
+ agreementType : AgreementType
23
+ createdAt : Time
24
+ description : Text
25
+ attachmentHashes : [Text]
26
+ deriving (Eq, Show)
27
+
28
+ data Signature = Signature
29
+ with
30
+ signer : Party
31
+ signedAt : Time
32
+ comments : Optional Text
33
+ deriving (Eq, Show)
34
+
35
+ -------------------------------------------------------------------------------
36
+ -- FINALIZED AGREEMENT
37
+ -------------------------------------------------------------------------------
38
+
39
+ template Agreement
40
+ with
41
+ signatories : [Party]
42
+ terms : Text
43
+ agreementId : Text
44
+ metadata : AgreementMetadata
45
+ signatures : [Signature]
46
+ finalizedAt : Time
47
+ where
48
+ signatory signatories
49
+
50
+ ensure uniqueParties signatories
51
+
52
+ choice ArchiveAgreement : ()
53
+ controller signatories
54
+ do
55
+ return ()
56
+
57
+ choice ProposeAmendment : ContractId AgreementProposal
58
+ with
59
+ proposer : Party
60
+ newTerms : Text
61
+ amendmentReason : Text
62
+ controller proposer
63
+ do
64
+ assertMsg "Proposer must be a signatory" (proposer `elem` signatories)
65
+ now <- getTime
66
+ create AgreementProposal with
67
+ proposer = proposer
68
+ allParties = signatories
69
+ alreadySigned = [proposer]
70
+ pendingParties = delete proposer signatories
71
+ terms = newTerms
72
+ agreementId = agreementId <> "-amendment"
73
+ metadata = metadata with
74
+ description = amendmentReason
75
+ createdAt = now
76
+ signatures = [Signature with
77
+ signer = proposer
78
+ signedAt = now
79
+ comments = Some amendmentReason]
80
+ requiredSignatures = length signatories
81
+ expiresAt = None
82
+
83
+ -------------------------------------------------------------------------------
84
+ -- AGREEMENT PROPOSAL
85
+ -------------------------------------------------------------------------------
86
+
87
+ template AgreementProposal
88
+ with
89
+ proposer : Party
90
+ allParties : [Party]
91
+ alreadySigned : [Party]
92
+ pendingParties : [Party]
93
+ terms : Text
94
+ agreementId : Text
95
+ metadata : AgreementMetadata
96
+ signatures : [Signature]
97
+ requiredSignatures : Int
98
+ expiresAt : Optional Time
99
+ where
100
+ signatory alreadySigned
101
+ observer allParties
102
+
103
+ ensure
104
+ uniqueParties allParties &&
105
+ uniqueParties alreadySigned &&
106
+ requiredSignatures > 0 &&
107
+ requiredSignatures <= length allParties &&
108
+ length alreadySigned <= requiredSignatures
109
+
110
+ choice Sign : ContractId AgreementProposal
111
+ with
112
+ signer : Party
113
+ signatureComments : Optional Text
114
+ controller signer
115
+ do
116
+ now <- getTime
117
+
118
+ assertMsg "Party is not in required signatories" (signer `elem` allParties)
119
+ assertMsg "Party has already signed" (signer `notElem` alreadySigned)
120
+ assertMsg "Party is not in pending list" (signer `elem` pendingParties)
121
+
122
+ case expiresAt of
123
+ Some expiry -> assertMsg "Proposal has expired" (now <= expiry)
124
+ None -> return ()
125
+
126
+ let newSignature = Signature with
127
+ signer = signer
128
+ signedAt = now
129
+ comments = signatureComments
130
+
131
+ create this with
132
+ alreadySigned = signer :: alreadySigned
133
+ pendingParties = delete signer pendingParties
134
+ signatures = newSignature :: signatures
135
+
136
+ choice Finalize : ContractId Agreement
137
+ controller proposer
138
+ do
139
+ now <- getTime
140
+ assertMsg "Not enough signatures"
141
+ (length alreadySigned >= requiredSignatures)
142
+
143
+ case expiresAt of
144
+ Some expiry -> assertMsg "Cannot finalize expired proposal" (now <= expiry)
145
+ None -> return ()
146
+
147
+ create Agreement with
148
+ signatories = sort alreadySigned
149
+ terms = terms
150
+ agreementId = agreementId
151
+ metadata = metadata
152
+ signatures = reverse signatures
153
+ finalizedAt = now
154
+
155
+ choice Reject : ()
156
+ with
157
+ rejector : Party
158
+ reason : Text
159
+ controller rejector
160
+ do
161
+ assertMsg "Only proposer or signatories can reject"
162
+ (rejector == proposer || rejector `elem` allParties)
163
+ return ()
164
+
165
+ choice UpdateExpiration : ContractId AgreementProposal
166
+ with
167
+ newExpiry : Optional Time
168
+ controller proposer
169
+ do
170
+ now <- getTime
171
+ case newExpiry of
172
+ Some expiry -> assertMsg "New expiry must be in future" (expiry > now)
173
+ None -> return ()
174
+ create this with expiresAt = newExpiry
175
+
176
+ choice AddRequiredParty : ContractId AgreementProposal
177
+ with
178
+ newParty : Party
179
+ controller proposer
180
+ do
181
+ assertMsg "Party is already required" (newParty `notElem` allParties)
182
+ create this with
183
+ allParties = newParty :: allParties
184
+ pendingParties = newParty :: pendingParties
185
+ requiredSignatures = requiredSignatures + 1
186
+
187
+ choice RemoveRequiredParty : ContractId AgreementProposal
188
+ with
189
+ partyToRemove : Party
190
+ controller proposer
191
+ do
192
+ assertMsg "Cannot remove party who has signed"
193
+ (partyToRemove `notElem` alreadySigned)
194
+ assertMsg "Party is not in required list"
195
+ (partyToRemove `elem` allParties)
196
+
197
+ create this with
198
+ allParties = delete partyToRemove allParties
199
+ pendingParties = delete partyToRemove pendingParties
200
+ requiredSignatures = requiredSignatures - 1
201
+
202
+ -------------------------------------------------------------------------------
203
+ -- HELPER FUNCTIONS
204
+ -------------------------------------------------------------------------------
205
+
206
+ uniqueParties : [Party] -> Bool
207
+ uniqueParties parties = length parties == length (dedup parties)
208
+
209
+ dedup : Eq a => [a] -> [a]
210
+ dedup [] = []
211
+ dedup (x::xs) = x :: dedup (filter (/= x) xs)
212
+
213
+ -------------------------------------------------------------------------------
214
+ -- TEST SCRIPTS
215
+ -------------------------------------------------------------------------------
216
+
217
+ setup : Script ()
218
+ setup = do
219
+ alice <- allocateParty "Alice"
220
+ bob <- allocateParty "Bob"
221
+ charlie <- allocateParty "Charlie"
222
+
223
+ now <- getTime
224
+
225
+ let meta = AgreementMetadata with
226
+ agreementType = Governance
227
+ createdAt = now
228
+ description = "Partnership agreement"
229
+ attachmentHashes = []
230
+
231
+ proposalCid <- submit alice $ createCmd AgreementProposal with
232
+ proposer = alice
233
+ allParties = [alice, bob, charlie]
234
+ alreadySigned = [alice]
235
+ pendingParties = [bob, charlie]
236
+ terms = "All parties agree to cooperate on the project"
237
+ agreementId = "AGREEMENT-001"
238
+ metadata = meta
239
+ signatures = [Signature with
240
+ signer = alice
241
+ signedAt = now
242
+ comments = Some "Initiated by Alice"]
243
+ requiredSignatures = 3
244
+ expiresAt = None
245
+
246
+ proposalCid2 <- submit bob $ exerciseCmd proposalCid Sign with
247
+ signer = bob
248
+ signatureComments = Some "Approved by Bob"
249
+
250
+ proposalCid3 <- submit charlie $ exerciseCmd proposalCid2 Sign with
251
+ signer = charlie
252
+ signatureComments = Some "Approved by Charlie"
253
+
254
+ submit alice $ exerciseCmd proposalCid3 Finalize
255
+
256
+ return ()
@@ -0,0 +1,15 @@
1
+ # Multi-Party Agreement System for Canton Network
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ sdk-version: 3.4.9
5
+ name: MultiPartyAgreement
6
+ source: daml/MultiPartyAgreement.daml
7
+ parties:
8
+ - Alice
9
+ - Bob
10
+ - Charlie
11
+ version: 1.0.0
12
+ dependencies:
13
+ - daml-prim
14
+ - daml-stdlib
15
+ - daml-script
@@ -0,0 +1,222 @@
1
+ -- | Token Transfer System for Canton Network
2
+ -- Supports 1-step and 2-step transfers, split/merge, locking
3
+ module Token where
4
+
5
+ import Daml.Script
6
+
7
+ -------------------------------------------------------------------------------
8
+ -- DATA TYPES
9
+ -------------------------------------------------------------------------------
10
+
11
+ data TransferMetadata = TransferMetadata
12
+ with
13
+ reason : Optional Text
14
+ reference : Optional Text
15
+ timestamp : Time
16
+ deriving (Eq, Show)
17
+
18
+ data TokenLock = TokenLock
19
+ with
20
+ lockHolder : Party
21
+ unlockCondition : Text
22
+ expiresAt : Optional Time
23
+ deriving (Eq, Show)
24
+
25
+ -------------------------------------------------------------------------------
26
+ -- MAIN TOKEN CONTRACT
27
+ -------------------------------------------------------------------------------
28
+
29
+ template Token
30
+ with
31
+ issuer : Party
32
+ owner : Party
33
+ amount : Decimal
34
+ tokenId : Text
35
+ symbol : Text
36
+ lock : Optional TokenLock
37
+ metadata : TransferMetadata
38
+ where
39
+ signatory issuer, owner
40
+ observer (case lock of
41
+ Some l -> [l.lockHolder]
42
+ None -> [])
43
+
44
+ ensure amount > 0.0
45
+
46
+ choice Transfer : ContractId Token
47
+ with
48
+ newOwner : Party
49
+ newMetadata : TransferMetadata
50
+ controller owner
51
+ do
52
+ assertMsg "Cannot transfer locked tokens" (isNone lock)
53
+ assertMsg "Cannot transfer to self" (newOwner /= owner)
54
+ create this with
55
+ owner = newOwner
56
+ metadata = newMetadata
57
+
58
+ choice ProposeTransfer : ContractId TransferProposal
59
+ with
60
+ newOwner : Party
61
+ proposalMetadata : TransferMetadata
62
+ expiresAt : Time
63
+ controller owner
64
+ do
65
+ assertMsg "Cannot propose transfer of locked tokens" (isNone lock)
66
+ assertMsg "Expiry must be in the future" (expiresAt > proposalMetadata.timestamp)
67
+ create TransferProposal with
68
+ tokenCid = self
69
+ currentOwner = owner
70
+ proposedOwner = newOwner
71
+ token = this
72
+ metadata = proposalMetadata
73
+ expiresAt = expiresAt
74
+
75
+ choice Split : (ContractId Token, ContractId Token)
76
+ with
77
+ splitAmount : Decimal
78
+ splitTime : Time
79
+ controller owner
80
+ do
81
+ assertMsg "Split amount must be positive" (splitAmount > 0.0)
82
+ assertMsg "Split amount must be less than total" (splitAmount < amount)
83
+ assertMsg "Cannot split locked tokens" (isNone lock)
84
+
85
+ let newMetadata = TransferMetadata with
86
+ reason = Some "Token split"
87
+ reference = metadata.reference
88
+ timestamp = splitTime
89
+
90
+ t1 <- create this with
91
+ amount = splitAmount
92
+ metadata = newMetadata
93
+ t2 <- create this with
94
+ amount = amount - splitAmount
95
+ metadata = newMetadata
96
+
97
+ return (t1, t2)
98
+
99
+ choice MergeToken : ContractId Token
100
+ with
101
+ otherTokenCid : ContractId Token
102
+ mergeTime : Time
103
+ controller owner
104
+ do
105
+ other <- fetch otherTokenCid
106
+ assertMsg "Issuers must match" (other.issuer == issuer)
107
+ assertMsg "Token IDs must match" (other.tokenId == tokenId)
108
+ assertMsg "Owners must match" (other.owner == owner)
109
+ assertMsg "Cannot merge locked tokens" (isNone lock && isNone other.lock)
110
+
111
+ exercise otherTokenCid Archive
112
+
113
+ create this with
114
+ amount = amount + other.amount
115
+ metadata = TransferMetadata with
116
+ reason = Some "Token merge"
117
+ reference = metadata.reference
118
+ timestamp = mergeTime
119
+
120
+ choice LockTokens : ContractId Token
121
+ with
122
+ lockData : TokenLock
123
+ controller owner
124
+ do
125
+ assertMsg "Token is already locked" (isNone lock)
126
+ create this with lock = Some lockData
127
+
128
+ choice UnlockTokens : ContractId Token
129
+ with
130
+ lockHolder : Party
131
+ controller lockHolder
132
+ do
133
+ assertMsg "Token is not locked" (isSome lock)
134
+ case lock of
135
+ Some l -> assertMsg "Not the lock holder" (l.lockHolder == lockHolder)
136
+ None -> abort "Token is not locked"
137
+ create this with lock = None
138
+
139
+ choice Burn : ()
140
+ controller owner
141
+ do
142
+ assertMsg "Cannot burn locked tokens" (isNone lock)
143
+ return ()
144
+
145
+ template TransferProposal
146
+ with
147
+ tokenCid : ContractId Token
148
+ currentOwner : Party
149
+ proposedOwner : Party
150
+ token : Token
151
+ metadata : TransferMetadata
152
+ expiresAt : Time
153
+ where
154
+ signatory currentOwner
155
+ observer proposedOwner
156
+
157
+ choice AcceptTransfer : ContractId Token
158
+ with
159
+ acceptTime : Time
160
+ controller proposedOwner
161
+ do
162
+ assertMsg "Transfer proposal has expired" (acceptTime <= expiresAt)
163
+ exercise tokenCid Archive
164
+ create token with
165
+ owner = proposedOwner
166
+ metadata = metadata with timestamp = acceptTime
167
+
168
+ choice RejectTransfer : ContractId Token
169
+ controller proposedOwner
170
+ do
171
+ return tokenCid
172
+
173
+ choice WithdrawProposal : ContractId Token
174
+ controller currentOwner
175
+ do
176
+ return tokenCid
177
+
178
+ -------------------------------------------------------------------------------
179
+ -- HELPER FUNCTIONS
180
+ -------------------------------------------------------------------------------
181
+
182
+ isNone : Optional a -> Bool
183
+ isNone opt = case opt of
184
+ None -> True
185
+ Some _ -> False
186
+
187
+ isSome : Optional a -> Bool
188
+ isSome opt = not (isNone opt)
189
+
190
+ -------------------------------------------------------------------------------
191
+ -- TEST SCRIPTS
192
+ -------------------------------------------------------------------------------
193
+
194
+ setup : Script ()
195
+ setup = do
196
+ alice <- allocateParty "Alice"
197
+ bob <- allocateParty "Bob"
198
+ issuer <- allocateParty "TokenIssuer"
199
+
200
+ now <- getTime
201
+
202
+ let transferMeta = TransferMetadata with
203
+ reason = Some "Initial allocation"
204
+ reference = None
205
+ timestamp = now
206
+
207
+ tokenCid <- submitMulti [issuer, alice] [] $ createCmd Token with
208
+ issuer = issuer
209
+ owner = alice
210
+ amount = 1000.0
211
+ tokenId = "TEST-TOKEN"
212
+ symbol = "TEST"
213
+ lock = None
214
+ metadata = transferMeta
215
+
216
+ submitMulti [alice, issuer] [] $ exerciseCmd tokenCid Transfer with
217
+ newOwner = bob
218
+ newMetadata = transferMeta with
219
+ reason = Some "Payment to Bob"
220
+ timestamp = now
221
+
222
+ return ()
@@ -0,0 +1,15 @@
1
+ # Token Transfer System for Canton Network
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ sdk-version: 3.4.9
5
+ name: TokenTransfer
6
+ source: daml/Token.daml
7
+ parties:
8
+ - TokenIssuer
9
+ - Alice
10
+ - Bob
11
+ version: 1.0.0
12
+ dependencies:
13
+ - daml-prim
14
+ - daml-stdlib
15
+ - daml-script
@@ -1,22 +0,0 @@
1
- -- Main.daml
2
- -- Your Canton smart contract starts here!
3
-
4
- module Main where
5
-
6
- import Daml.Script
7
-
8
- template MyContract
9
- with
10
- party1 : Party
11
- where
12
- signatory party1
13
-
14
- setup : Script ()
15
- setup = script do
16
- alice <- allocateParty "Alice"
17
-
18
- submit alice do
19
- createCmd MyContract with
20
- party1 = alice
21
-
22
- return ()
@@ -1,53 +0,0 @@
1
- -- Escrow.daml
2
- -- A two-party escrow with an arbiter
3
-
4
- module Escrow where
5
-
6
- import Daml.Script
7
-
8
- template EscrowAgreement
9
- with
10
- buyer : Party
11
- seller : Party
12
- arbiter : Party
13
- amount : Decimal
14
- description : Text
15
- where
16
- signatory buyer, seller, arbiter
17
-
18
- choice Release : ()
19
- controller buyer
20
- do
21
- return ()
22
-
23
- choice Dispute : ()
24
- with
25
- decision : Text
26
- controller arbiter
27
- do
28
- return ()
29
-
30
- choice Cancel : ()
31
- controller buyer, seller
32
- do
33
- return ()
34
-
35
- setup : Script ()
36
- setup = script do
37
- alice <- allocateParty "Alice"
38
- bob <- allocateParty "Bob"
39
- charlie <- allocateParty "Charlie"
40
-
41
- escrow <- submit alice do
42
- submitMulti [alice, bob, charlie] [] do
43
- createCmd EscrowAgreement with
44
- buyer = alice
45
- seller = bob
46
- arbiter = charlie
47
- amount = 1000.0
48
- description = "Laptop purchase"
49
-
50
- submit alice do
51
- exerciseCmd escrow Release
52
-
53
- return ()
@@ -1,56 +0,0 @@
1
- -- Token.daml
2
- -- A simple fungible token on Canton Network
3
-
4
- module Token where
5
-
6
- import Daml.Script
7
-
8
- -- The Token template represents a token holding
9
- template Token
10
- with
11
- issuer : Party
12
- owner : Party
13
- amount : Decimal
14
- symbol : Text
15
- where
16
- signatory issuer
17
- observer owner
18
-
19
- -- Transfer tokens to another party
20
- choice Transfer : ContractId Token
21
- with
22
- newOwner : Party
23
- controller owner
24
- do
25
- create this with owner = newOwner
26
-
27
- -- Split tokens (for partial transfers)
28
- choice Split : (ContractId Token, ContractId Token)
29
- with
30
- splitAmount : Decimal
31
- controller owner
32
- do
33
- assertMsg "Split amount must be less than total" (splitAmount < amount)
34
- token1 <- create this with amount = splitAmount
35
- token2 <- create this with amount = amount - splitAmount
36
- return (token1, token2)
37
-
38
- -- Example: How to use this in practice
39
- setup : Script ()
40
- setup = script do
41
- alice <- allocateParty "Alice"
42
- bob <- allocateParty "Bob"
43
- issuer <- allocateParty "TokenIssuer"
44
-
45
- aliceToken <- submit issuer do
46
- createCmd Token with
47
- issuer = issuer
48
- owner = alice
49
- amount = 1000.0
50
- symbol = "TEST"
51
-
52
- submit alice do
53
- exerciseCmd aliceToken Transfer with
54
- newOwner = bob
55
-
56
- return ()