@openzeppelin/wizard-stellar 0.6.5 → 0.7.0

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.
Files changed (60) hide show
  1. package/dist/account.d.ts.map +1 -1
  2. package/dist/account.js +18 -4
  3. package/dist/account.js.map +1 -1
  4. package/dist/add-votes.d.ts +3 -0
  5. package/dist/add-votes.d.ts.map +1 -0
  6. package/dist/add-votes.js +53 -0
  7. package/dist/add-votes.js.map +1 -0
  8. package/dist/fungible.d.ts +13 -2
  9. package/dist/fungible.d.ts.map +1 -1
  10. package/dist/fungible.js +146 -76
  11. package/dist/fungible.js.map +1 -1
  12. package/dist/generate/fungible.d.ts.map +1 -1
  13. package/dist/generate/fungible.js +2 -0
  14. package/dist/generate/fungible.js.map +1 -1
  15. package/dist/generate/non-fungible.d.ts.map +1 -1
  16. package/dist/generate/non-fungible.js +2 -0
  17. package/dist/generate/non-fungible.js.map +1 -1
  18. package/dist/generate/stablecoin.d.ts.map +1 -1
  19. package/dist/generate/stablecoin.js +2 -0
  20. package/dist/generate/stablecoin.js.map +1 -1
  21. package/dist/governor.d.ts.map +1 -1
  22. package/dist/governor.js +9 -1
  23. package/dist/governor.js.map +1 -1
  24. package/dist/non-fungible.d.ts +2 -0
  25. package/dist/non-fungible.d.ts.map +1 -1
  26. package/dist/non-fungible.js +152 -126
  27. package/dist/non-fungible.js.map +1 -1
  28. package/dist/print.js +16 -1
  29. package/dist/print.js.map +1 -1
  30. package/dist/stablecoin.d.ts.map +1 -1
  31. package/dist/stablecoin.js +6 -31
  32. package/dist/stablecoin.js.map +1 -1
  33. package/dist/utils/compose.d.ts +6 -0
  34. package/dist/utils/compose.d.ts.map +1 -0
  35. package/dist/utils/compose.js +12 -0
  36. package/dist/utils/compose.js.map +1 -0
  37. package/dist/utils/version.d.ts +4 -4
  38. package/dist/utils/version.js +3 -3
  39. package/dist/vault.d.ts +1 -1
  40. package/dist/vault.d.ts.map +1 -1
  41. package/dist/vault.js +27 -7
  42. package/dist/vault.js.map +1 -1
  43. package/dist/zip-shared.d.ts.map +1 -1
  44. package/dist/zip-shared.js +4 -3
  45. package/dist/zip-shared.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/account.ts +18 -4
  48. package/src/add-votes.ts +53 -0
  49. package/src/fungible.ts +170 -85
  50. package/src/generate/fungible.ts +2 -0
  51. package/src/generate/non-fungible.ts +2 -0
  52. package/src/generate/stablecoin.ts +2 -0
  53. package/src/governor.ts +10 -1
  54. package/src/non-fungible.ts +182 -143
  55. package/src/print.ts +14 -1
  56. package/src/stablecoin.ts +9 -37
  57. package/src/utils/compose.ts +8 -0
  58. package/src/utils/version.ts +3 -3
  59. package/src/vault.ts +32 -7
  60. package/src/zip-shared.ts +4 -3
@@ -1,18 +1,23 @@
1
1
  import { pickKeys } from '@openzeppelin/wizard-common';
2
2
  import { addPausable } from './add-pausable';
3
3
  import { addUpgradeable } from './add-upgradeable';
4
+ import { addVotes } from './add-votes';
4
5
  import type { CommonContractOptions } from './common-options';
5
6
  import { contractDefaults as commonDefaults, getSelfArg, withCommonContractDefaults } from './common-options';
6
- import type { Contract } from './contract';
7
+ import type { BaseFunction, Contract } from './contract';
7
8
  import { ContractBuilder } from './contract';
8
9
  import type { OptionsErrorMessages } from './error';
9
10
  import { OptionsError } from './error';
10
11
  import { printContract } from './print';
11
12
  import { type Access, DEFAULT_ACCESS_CONTROL, requireAccessControl, setAccessControl } from './set-access-control';
12
13
  import { setInfo } from './set-info';
13
- import { toByteArray } from './utils/convert-strings';
14
+ import { printComposedContractType } from './utils/compose';
15
+ import { toByteArray, toUint } from './utils/convert-strings';
14
16
  import { defineFunctions } from './utils/define-functions';
15
17
 
18
+ // Upper bound enforced by the library's royalty setters (100% in basis points).
19
+ const MAX_ROYALTY_BASIS_POINTS = 10_000n;
20
+
16
21
  export const defaults: Required<NonFungibleOptions> = {
17
22
  name: 'MyToken',
18
23
  symbol: 'MTK',
@@ -21,6 +26,8 @@ export const defaults: Required<NonFungibleOptions> = {
21
26
  votes: false,
22
27
  enumerable: false,
23
28
  consecutive: false,
29
+ royalties: false,
30
+ defaultRoyaltyBasisPoints: '0',
24
31
  pausable: false,
25
32
  upgradeable: false,
26
33
  mintable: false,
@@ -42,6 +49,8 @@ export interface NonFungibleOptions extends CommonContractOptions {
42
49
  votes?: boolean;
43
50
  enumerable?: boolean;
44
51
  consecutive?: boolean;
52
+ royalties?: boolean;
53
+ defaultRoyaltyBasisPoints?: string;
45
54
  pausable?: boolean;
46
55
  upgradeable?: boolean;
47
56
  mintable?: boolean;
@@ -57,6 +66,8 @@ function withDefaults(opts: NonFungibleOptions): Required<NonFungibleOptions> {
57
66
  votes: opts.votes ?? defaults.votes,
58
67
  consecutive: opts.consecutive ?? defaults.consecutive,
59
68
  enumerable: opts.enumerable ?? defaults.enumerable,
69
+ royalties: opts.royalties ?? defaults.royalties,
70
+ defaultRoyaltyBasisPoints: opts.defaultRoyaltyBasisPoints || defaults.defaultRoyaltyBasisPoints,
60
71
  pausable: opts.pausable ?? defaults.pausable,
61
72
  upgradeable: opts.upgradeable ?? defaults.upgradeable,
62
73
  mintable: opts.mintable ?? defaults.mintable,
@@ -65,7 +76,13 @@ function withDefaults(opts: NonFungibleOptions): Required<NonFungibleOptions> {
65
76
  }
66
77
 
67
78
  export function isAccessControlRequired(opts: Partial<NonFungibleOptions>): boolean {
68
- return opts.mintable === true || opts.pausable === true || opts.upgradeable === true || opts.consecutive === true;
79
+ return (
80
+ opts.mintable === true ||
81
+ opts.pausable === true ||
82
+ opts.upgradeable === true ||
83
+ opts.consecutive === true ||
84
+ opts.royalties === true
85
+ );
69
86
  }
70
87
 
71
88
  export function buildNonFungible(opts: NonFungibleOptions): Contract {
@@ -90,30 +107,35 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
90
107
  errors.sequential = 'Sequential minting cannot be used with Consecutive extension';
91
108
  }
92
109
 
93
- if (allOpts.votes && allOpts.enumerable) {
94
- errors.votes = 'Votes extension cannot be used with Enumerable extension';
95
- errors.enumerable = 'Enumerable extension cannot be used with Votes extension';
96
- }
97
-
98
- if (allOpts.votes && allOpts.consecutive) {
99
- errors.votes = 'Votes extension cannot be used with Consecutive extension';
100
- errors.consecutive = 'Consecutive extension cannot be used with Votes extension';
101
- }
102
-
103
110
  if (Object.keys(errors).length > 0) {
104
111
  throw new OptionsError(errors);
105
112
  }
106
113
 
114
+ // Order follows the library's documentation, e.g. `Compose<(Enumerable, NonFungibleVotes)>`.
115
+ const contractTypes = [
116
+ ...(allOpts.enumerable ? ['Enumerable'] : []),
117
+ ...(allOpts.consecutive ? ['Consecutive'] : []),
118
+ ...(allOpts.votes ? ['NonFungibleVotes'] : []),
119
+ ];
120
+
107
121
  addBase(
108
122
  c,
109
123
  toByteArray(allOpts.name),
110
124
  toByteArray(allOpts.symbol),
111
125
  toByteArray(allOpts.tokenUri),
112
- allOpts.votes,
126
+ contractTypes,
113
127
  allOpts.pausable,
114
128
  allOpts.explicitImplementations,
115
129
  );
116
130
 
131
+ if (allOpts.votes) {
132
+ addVotes(c, allOpts.explicitImplementations);
133
+ }
134
+
135
+ if (allOpts.royalties) {
136
+ addRoyalties(c, allOpts.defaultRoyaltyBasisPoints, allOpts.access, allOpts.explicitImplementations);
137
+ }
138
+
117
139
  if (allOpts.pausable) {
118
140
  addPausable(c, allOpts.access, allOpts.explicitImplementations);
119
141
  }
@@ -135,15 +157,7 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
135
157
  }
136
158
 
137
159
  if (allOpts.mintable) {
138
- addMintable(
139
- c,
140
- allOpts.enumerable,
141
- allOpts.votes,
142
- allOpts.pausable,
143
- allOpts.sequential,
144
- allOpts.access,
145
- allOpts.explicitImplementations,
146
- );
160
+ addMintable(c, allOpts.pausable, allOpts.sequential, allOpts.access, allOpts.explicitImplementations);
147
161
  }
148
162
 
149
163
  setAccessControl(c, allOpts.access, allOpts.explicitImplementations);
@@ -157,7 +171,7 @@ function addBase(
157
171
  name: string,
158
172
  symbol: string,
159
173
  tokenUri: string,
160
- votes: boolean,
174
+ contractTypes: string[],
161
175
  pausable: boolean,
162
176
  explicitImplementations: boolean,
163
177
  ) {
@@ -169,9 +183,9 @@ function addBase(
169
183
 
170
184
  // Set token functions
171
185
  c.addUseClause('stellar_tokens::non_fungible', 'Base');
186
+ c.addUseClause('stellar_tokens::non_fungible', 'Compose');
172
187
  c.addUseClause('stellar_tokens::non_fungible', 'NonFungibleToken');
173
- if (votes) {
174
- c.addUseClause('stellar_governance::votes', 'Votes');
188
+ if (contractTypes.includes('NonFungibleVotes')) {
175
189
  c.addUseClause('stellar_tokens::non_fungible', 'votes::NonFungibleVotes');
176
190
  }
177
191
  if (explicitImplementations) c.addUseClause('stellar_tokens::non_fungible', 'ContractOverrides');
@@ -185,7 +199,7 @@ function addBase(
185
199
  traitName: 'NonFungibleToken',
186
200
  structName: c.name,
187
201
  tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
188
- assocType: `type ContractType = ${votes ? 'NonFungibleVotes' : 'Base'};`,
202
+ assocType: printComposedContractType(contractTypes.length > 0 ? contractTypes : ['Base']),
189
203
  };
190
204
 
191
205
  c.addTraitImplBlock(nonFungibleTokenTrait);
@@ -201,15 +215,6 @@ function addBase(
201
215
  c.addFunctionTag(baseFunctions.transfer_from, 'when_not_paused', nonFungibleTokenTrait);
202
216
  c.addTraitFunction(nonFungibleTokenTrait, baseFunctions.transfer_from);
203
217
  }
204
-
205
- if (votes) {
206
- c.addTraitImplBlock({
207
- traitName: 'Votes',
208
- structName: c.name,
209
- tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
210
- section: 'Extensions',
211
- });
212
- }
213
218
  }
214
219
 
215
220
  function addBurnable(c: ContractBuilder, pausable: boolean, explicitImplementations: boolean) {
@@ -249,8 +254,6 @@ function addEnumerable(c: ContractBuilder, explicitImplementations: boolean) {
249
254
  if (explicitImplementations)
250
255
  c.addTraitForEachFunctions(nonFungibleEnumerableTrait, nonFungibleEnumerableTraitFunctions);
251
256
  else c.addTraitImplBlock(nonFungibleEnumerableTrait);
252
-
253
- c.overrideAssocType('NonFungibleToken', 'type ContractType = Enumerable;');
254
257
  }
255
258
 
256
259
  function addConsecutive(c: ContractBuilder, pausable: boolean, access: Access, explicitImplementations: boolean) {
@@ -266,10 +269,8 @@ function addConsecutive(c: ContractBuilder, pausable: boolean, access: Access, e
266
269
 
267
270
  c.addTraitImplBlock(nonFungibleConsecutiveTrait);
268
271
 
269
- c.overrideAssocType('NonFungibleToken', 'type ContractType = Consecutive;');
270
-
271
272
  const mintFn =
272
- effectiveAccess === 'ownable' ? consecutiveFunctions.batch_mint : consecutiveFunctions.batch_mint_with_caller;
273
+ effectiveAccess === 'ownable' ? consecutiveFunctions.mint_range : consecutiveFunctions.mint_range_with_caller;
273
274
  c.addFreeFunction(mintFn);
274
275
  if (pausable) {
275
276
  c.addFunctionTag(mintFn, 'when_not_paused');
@@ -291,8 +292,6 @@ function addConsecutive(c: ContractBuilder, pausable: boolean, access: Access, e
291
292
 
292
293
  function addMintable(
293
294
  c: ContractBuilder,
294
- enumerable: boolean,
295
- votes: boolean,
296
295
  pausable: boolean,
297
296
  sequential: boolean,
298
297
  access: Access,
@@ -301,40 +300,11 @@ function addMintable(
301
300
  const accessProps = { useMacro: true, role: 'minter', caller: 'caller' };
302
301
  const effectiveAccess = access === false ? DEFAULT_ACCESS_CONTROL : access;
303
302
 
304
- let mintFn;
305
-
306
- if (enumerable) {
307
- if (sequential) {
308
- mintFn =
309
- effectiveAccess === 'ownable'
310
- ? enumerableFunctions.sequential_mint
311
- : enumerableFunctions.sequential_mint_with_caller;
312
- } else {
313
- mintFn =
314
- effectiveAccess === 'ownable'
315
- ? enumerableFunctions.non_sequential_mint
316
- : enumerableFunctions.non_sequential_mint_with_caller;
317
- }
303
+ let mintFn: BaseFunction;
304
+ if (sequential) {
305
+ mintFn = effectiveAccess === 'ownable' ? mintFunctions.mint : mintFunctions.mint_with_caller;
318
306
  } else {
319
- if (sequential) {
320
- mintFn =
321
- effectiveAccess === 'ownable'
322
- ? votes
323
- ? votesMintFunctions.sequential_mint
324
- : baseFunctions.sequential_mint
325
- : votes
326
- ? votesMintFunctions.sequential_mint_with_caller
327
- : baseFunctions.sequential_mint_with_caller;
328
- } else {
329
- mintFn =
330
- effectiveAccess === 'ownable'
331
- ? votes
332
- ? votesMintFunctions.mint
333
- : baseFunctions.mint
334
- : votes
335
- ? votesMintFunctions.mint_with_caller
336
- : baseFunctions.mint_with_caller;
337
- }
307
+ mintFn = effectiveAccess === 'ownable' ? mintFunctions.mint_with_id : mintFunctions.mint_with_id_with_caller;
338
308
  }
339
309
 
340
310
  c.addFreeFunction(mintFn);
@@ -345,6 +315,67 @@ function addMintable(
345
315
  }
346
316
  }
347
317
 
318
+ function addRoyalties(
319
+ c: ContractBuilder,
320
+ defaultRoyaltyBasisPoints: string,
321
+ access: Access,
322
+ explicitImplementations: boolean,
323
+ ) {
324
+ const basisPoints = toUint(defaultRoyaltyBasisPoints, 'defaultRoyaltyBasisPoints', 'u32');
325
+ if (basisPoints > MAX_ROYALTY_BASIS_POINTS) {
326
+ throw new OptionsError({
327
+ defaultRoyaltyBasisPoints: `Maximum royalty is ${MAX_ROYALTY_BASIS_POINTS} basis points (100%)`,
328
+ });
329
+ }
330
+
331
+ c.addUseClause('stellar_tokens::non_fungible', 'royalties::{NonFungibleRoyalties, RoyaltySupport}');
332
+
333
+ if (basisPoints > 0n) {
334
+ c.addConstructorArgument({ name: 'royalty_receiver', type: 'Address' });
335
+ c.addConstructorCode(`Base::set_default_royalty(e, &royalty_receiver, ${basisPoints});`);
336
+ }
337
+
338
+ const nonFungibleRoyaltiesTrait = {
339
+ traitName: 'NonFungibleRoyalties',
340
+ structName: c.name,
341
+ tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
342
+ section: 'Extensions',
343
+ };
344
+
345
+ const effectiveAccess = access === false ? DEFAULT_ACCESS_CONTROL : access;
346
+ const settersFns =
347
+ effectiveAccess === 'ownable'
348
+ ? [
349
+ royaltiesFunctions.set_default_royalty_unused_operator,
350
+ royaltiesFunctions.set_token_royalty_unused_operator,
351
+ royaltiesFunctions.remove_token_royalty_unused_operator,
352
+ ]
353
+ : [
354
+ royaltiesFunctions.set_default_royalty,
355
+ royaltiesFunctions.set_token_royalty,
356
+ royaltiesFunctions.remove_token_royalty,
357
+ ];
358
+
359
+ // Setting royalties is privileged, so the library provides no default implementation for the setters.
360
+ for (const fn of settersFns) {
361
+ c.addTraitFunction(nonFungibleRoyaltiesTrait, fn);
362
+ requireAccessControl(
363
+ c,
364
+ nonFungibleRoyaltiesTrait,
365
+ fn,
366
+ effectiveAccess,
367
+ {
368
+ useMacro: true,
369
+ role: 'royalty_admin',
370
+ caller: 'operator',
371
+ },
372
+ explicitImplementations,
373
+ );
374
+ }
375
+
376
+ if (explicitImplementations) c.addTraitFunction(nonFungibleRoyaltiesTrait, royaltiesFunctions.royalty_info);
377
+ }
378
+
348
379
  const baseFunctions = defineFunctions({
349
380
  // NonFungible Trait
350
381
  balance: {
@@ -420,40 +451,27 @@ const baseFunctions = defineFunctions({
420
451
  returns: 'String',
421
452
  code: ['Self::ContractType::token_uri(e, token_id)'],
422
453
  },
454
+ });
423
455
 
424
- // Mint
456
+ // Minting goes through the contract type, which keeps the enumeration and the voting units up to date.
457
+ const mintFunctions = defineFunctions({
425
458
  mint: {
426
- args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'token_id', type: 'u32' }],
427
- code: ['Base::mint(e, &to, token_id);'],
428
- },
429
- mint_with_caller: {
430
- name: 'mint',
431
- args: [
432
- getSelfArg(),
433
- { name: 'to', type: 'Address' },
434
- { name: 'token_id', type: 'u32' },
435
- { name: 'caller', type: 'Address' },
436
- ],
437
- code: ['Base::mint(e, &to, token_id);'],
438
- },
439
- sequential_mint: {
440
- name: 'mint',
441
459
  args: [getSelfArg(), { name: 'to', type: 'Address' }],
442
- code: ['Base::sequential_mint(e, &to);'],
460
+ returns: 'u32',
461
+ code: ['<Self as NonFungibleToken>::ContractType::mint(e, &to)'],
443
462
  },
444
- sequential_mint_with_caller: {
463
+ mint_with_caller: {
445
464
  name: 'mint',
446
465
  args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'caller', type: 'Address' }],
447
- code: ['Base::sequential_mint(e, &to);'],
466
+ returns: 'u32',
467
+ code: ['<Self as NonFungibleToken>::ContractType::mint(e, &to)'],
448
468
  },
449
- });
450
-
451
- const votesMintFunctions = defineFunctions({
452
- mint: {
469
+ mint_with_id: {
470
+ name: 'mint',
453
471
  args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'token_id', type: 'u32' }],
454
- code: ['NonFungibleVotes::mint(e, &to, token_id);'],
472
+ code: ['<Self as NonFungibleToken>::ContractType::mint_with_id(e, &to, token_id);'],
455
473
  },
456
- mint_with_caller: {
474
+ mint_with_id_with_caller: {
457
475
  name: 'mint',
458
476
  args: [
459
477
  getSelfArg(),
@@ -461,17 +479,7 @@ const votesMintFunctions = defineFunctions({
461
479
  { name: 'token_id', type: 'u32' },
462
480
  { name: 'caller', type: 'Address' },
463
481
  ],
464
- code: ['NonFungibleVotes::mint(e, &to, token_id);'],
465
- },
466
- sequential_mint: {
467
- name: 'mint',
468
- args: [getSelfArg(), { name: 'to', type: 'Address' }],
469
- code: ['NonFungibleVotes::sequential_mint(e, &to);'],
470
- },
471
- sequential_mint_with_caller: {
472
- name: 'mint',
473
- args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'caller', type: 'Address' }],
474
- code: ['NonFungibleVotes::sequential_mint(e, &to);'],
482
+ code: ['<Self as NonFungibleToken>::ContractType::mint_with_id(e, &to, token_id);'],
475
483
  },
476
484
  });
477
485
 
@@ -523,31 +531,6 @@ const enumerableFunctions = defineFunctions({
523
531
  returns: 'u32',
524
532
  code: ['Enumerable::get_token_id(e, index)'],
525
533
  },
526
- non_sequential_mint: {
527
- name: 'mint',
528
- args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'token_id', type: 'u32' }],
529
- code: ['Enumerable::non_sequential_mint(e, &to, token_id);'],
530
- },
531
- non_sequential_mint_with_caller: {
532
- name: 'mint',
533
- args: [
534
- getSelfArg(),
535
- { name: 'to', type: 'Address' },
536
- { name: 'token_id', type: 'u32' },
537
- { name: 'caller', type: 'Address' },
538
- ],
539
- code: ['Enumerable::non_sequential_mint(e, &to, token_id);'],
540
- },
541
- sequential_mint: {
542
- name: 'mint',
543
- args: [getSelfArg(), { name: 'to', type: 'Address' }],
544
- code: ['Enumerable::sequential_mint(e, &to);'],
545
- },
546
- sequential_mint_with_caller: {
547
- name: 'mint',
548
- args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'caller', type: 'Address' }],
549
- code: ['Enumerable::sequential_mint(e, &to);'],
550
- },
551
534
  });
552
535
 
553
536
  const nonFungibleEnumerableTraitFunctions = pickKeys(enumerableFunctions, [
@@ -557,14 +540,13 @@ const nonFungibleEnumerableTraitFunctions = pickKeys(enumerableFunctions, [
557
540
  ]);
558
541
 
559
542
  const consecutiveFunctions = defineFunctions({
560
- batch_mint: {
561
- name: 'batch_mint',
543
+ mint_range: {
562
544
  args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'amount', type: 'u32' }],
563
545
  returns: 'u32',
564
- code: ['Consecutive::batch_mint(e, &to, amount)'],
546
+ code: ['<Self as NonFungibleToken>::ContractType::mint_range(e, &to, amount)'],
565
547
  },
566
- batch_mint_with_caller: {
567
- name: 'batch_mint',
548
+ mint_range_with_caller: {
549
+ name: 'mint_range',
568
550
  args: [
569
551
  getSelfArg(),
570
552
  { name: 'to', type: 'Address' },
@@ -572,6 +554,63 @@ const consecutiveFunctions = defineFunctions({
572
554
  { name: 'caller', type: 'Address' },
573
555
  ],
574
556
  returns: 'u32',
575
- code: ['Consecutive::batch_mint(e, &to, amount)'],
557
+ code: ['<Self as NonFungibleToken>::ContractType::mint_range(e, &to, amount)'],
558
+ },
559
+ });
560
+
561
+ const royaltiesFunctions = defineFunctions({
562
+ set_default_royalty: {
563
+ args: [
564
+ getSelfArg(),
565
+ { name: 'receiver', type: 'Address' },
566
+ { name: 'basis_points', type: 'u32' },
567
+ { name: 'operator', type: 'Address' },
568
+ ],
569
+ code: ['Base::set_default_royalty(e, &receiver, basis_points)'],
570
+ },
571
+ set_default_royalty_unused_operator: {
572
+ name: 'set_default_royalty',
573
+ args: [
574
+ getSelfArg(),
575
+ { name: 'receiver', type: 'Address' },
576
+ { name: 'basis_points', type: 'u32' },
577
+ { name: '_operator', type: 'Address' },
578
+ ],
579
+ code: ['Base::set_default_royalty(e, &receiver, basis_points)'],
580
+ },
581
+ set_token_royalty: {
582
+ args: [
583
+ getSelfArg(),
584
+ { name: 'token_id', type: 'u32' },
585
+ { name: 'receiver', type: 'Address' },
586
+ { name: 'basis_points', type: 'u32' },
587
+ { name: 'operator', type: 'Address' },
588
+ ],
589
+ code: ['Self::ContractType::set_token_royalty(e, token_id, &receiver, basis_points)'],
590
+ },
591
+ set_token_royalty_unused_operator: {
592
+ name: 'set_token_royalty',
593
+ args: [
594
+ getSelfArg(),
595
+ { name: 'token_id', type: 'u32' },
596
+ { name: 'receiver', type: 'Address' },
597
+ { name: 'basis_points', type: 'u32' },
598
+ { name: '_operator', type: 'Address' },
599
+ ],
600
+ code: ['Self::ContractType::set_token_royalty(e, token_id, &receiver, basis_points)'],
601
+ },
602
+ remove_token_royalty: {
603
+ args: [getSelfArg(), { name: 'token_id', type: 'u32' }, { name: 'operator', type: 'Address' }],
604
+ code: ['Self::ContractType::remove_token_royalty(e, token_id)'],
605
+ },
606
+ remove_token_royalty_unused_operator: {
607
+ name: 'remove_token_royalty',
608
+ args: [getSelfArg(), { name: 'token_id', type: 'u32' }, { name: '_operator', type: 'Address' }],
609
+ code: ['Self::ContractType::remove_token_royalty(e, token_id)'],
610
+ },
611
+ royalty_info: {
612
+ args: [getSelfArg(), { name: 'token_id', type: 'u32' }, { name: 'sale_price', type: 'i128' }],
613
+ returns: '(Address, i128)',
614
+ code: ['Self::ContractType::royalty_info(e, token_id, sale_price)'],
576
615
  },
577
616
  });
package/src/print.ts CHANGED
@@ -139,7 +139,7 @@ function splitLongLineInner(line: string): Lines[] {
139
139
  const lines = [];
140
140
  if (line.length > MAX_USE_CLAUSE_LINE_LENGTH) {
141
141
  const max_accessible_string = line.slice(0, MAX_USE_CLAUSE_LINE_LENGTH);
142
- const lastCommaIndex = max_accessible_string.lastIndexOf(',');
142
+ const lastCommaIndex = lastTopLevelCommaIndex(max_accessible_string);
143
143
  if (lastCommaIndex !== -1) {
144
144
  lines.push(TAB + max_accessible_string.slice(0, lastCommaIndex + 1));
145
145
  lines.push(...splitLongLineInner(line.slice(lastCommaIndex + 2)));
@@ -152,6 +152,19 @@ function splitLongLineInner(line: string): Lines[] {
152
152
  return lines;
153
153
  }
154
154
 
155
+ // Prefers breaking between imports over breaking inside a nested group like `total_supply::{A, B}`.
156
+ function lastTopLevelCommaIndex(line: string): number {
157
+ let depth = 0;
158
+ let lastIndex = -1;
159
+ for (let i = 0; i < line.length; i++) {
160
+ const char = line[i];
161
+ if (char === '{') depth++;
162
+ else if (char === '}') depth--;
163
+ else if (char === ',' && depth === 0) lastIndex = i;
164
+ }
165
+ return lastIndex !== -1 ? lastIndex : line.lastIndexOf(',');
166
+ }
167
+
155
168
  function sortUseClauses(contract: Contract): UseClause[] {
156
169
  return contract.useClauses.sort((a, b) => {
157
170
  if (a.containerPath === b.containerPath) {
package/src/stablecoin.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { getSelfArg } from './common-options';
2
2
  import type { Contract, ContractBuilder } from './contract';
3
- import type { FungibleOptions } from './fungible';
3
+ import type { FungibleOptions, TransferPolicy } from './fungible';
4
4
  import {
5
- buildFungible,
5
+ buildFungibleWithTransferPolicy,
6
6
  defaults as fungibleDefaults,
7
7
  functions as fungibleFunctions,
8
8
  isAccessControlRequired as fungibleIsAccessControlRequired,
@@ -11,7 +11,6 @@ import {
11
11
  import { printContract } from './print';
12
12
  import { requireAccessControl, type Access } from './set-access-control';
13
13
  import { defineFunctions } from './utils/define-functions';
14
- import { OptionsError } from './error';
15
14
 
16
15
  export const defaults: Required<StablecoinOptions> = {
17
16
  ...fungibleDefaults,
@@ -23,6 +22,11 @@ export const defaults: Required<StablecoinOptions> = {
23
22
  export const limitationsOptions = [false, 'allowlist', 'blocklist'] as const;
24
23
  export type Limitations = (typeof limitationsOptions)[number];
25
24
 
25
+ const transferPolicies = {
26
+ allowlist: 'AllowList',
27
+ blocklist: 'BlockList',
28
+ } as const satisfies Record<Exclude<Limitations, false>, TransferPolicy>;
29
+
26
30
  export function printStablecoin(opts: StablecoinOptions = defaults): string {
27
31
  return printContract(buildStablecoin(opts));
28
32
  }
@@ -47,14 +51,8 @@ export function isAccessControlRequired(opts: Partial<StablecoinOptions>): boole
47
51
  export function buildStablecoin(opts: StablecoinOptions): Contract {
48
52
  const allOpts = withDefaults(opts);
49
53
 
50
- if (allOpts.votes && allOpts.limitations) {
51
- throw new OptionsError({
52
- votes: 'Votes extension cannot be used with stablecoin limitations',
53
- limitations: 'Stablecoin limitations cannot be used with Votes extension',
54
- });
55
- }
56
-
57
- const c = buildFungible(allOpts);
54
+ const transferPolicy = allOpts.limitations ? transferPolicies[allOpts.limitations] : undefined;
55
+ const c = buildFungibleWithTransferPolicy(allOpts, transferPolicy);
58
56
 
59
57
  if (allOpts.limitations) {
60
58
  addLimitations(c, allOpts.access, allOpts.limitations, allOpts.explicitImplementations);
@@ -80,14 +78,10 @@ function addLimitations(
80
78
 
81
79
  if (type) {
82
80
  c.addUseClause('stellar_tokens::fungible', 'allowlist::{AllowList, FungibleAllowList}');
83
- c.overrideAssocType('FungibleToken', 'type ContractType = AllowList;');
84
81
  } else {
85
82
  c.addUseClause('stellar_tokens::fungible', 'blocklist::{BlockList, FungibleBlockList}');
86
- c.overrideAssocType('FungibleToken', 'type ContractType = BlockList;');
87
83
  }
88
84
 
89
- if (explicitImplementations) overrideFungibleReadonlyFunctionsWithBase(c);
90
-
91
85
  const [getterFn, addFn, removeFn] = type
92
86
  ? [functions.allowed, functions.allow_user, functions.disallow_user]
93
87
  : [functions.blocked, functions.block_user, functions.unblock_user];
@@ -107,28 +101,6 @@ function addLimitations(
107
101
  requireAccessControl(c, limitationsTrait, removeFn, access, accessProps, explicitImplementations);
108
102
  }
109
103
 
110
- function overrideFungibleReadonlyFunctionsWithBase(c: ContractBuilder) {
111
- c.addUseClause('stellar_tokens::fungible', 'ContractOverrides');
112
- const fungibleTokenTrait = {
113
- traitName: 'FungibleToken',
114
- structName: c.name,
115
- tags: [],
116
- };
117
-
118
- const overrides: Array<[(typeof fungibleFunctions)[keyof typeof fungibleFunctions], string[]]> = [
119
- [fungibleFunctions.total_supply, ['Self::ContractType::total_supply(e)']],
120
- [fungibleFunctions.balance, ['Self::ContractType::balance(e, &account)']],
121
- [fungibleFunctions.allowance, ['Self::ContractType::allowance(e, &owner, &spender)']],
122
- [fungibleFunctions.decimals, ['Self::ContractType::decimals(e)']],
123
- [fungibleFunctions.name, ['Self::ContractType::name(e)']],
124
- [fungibleFunctions.symbol, ['Self::ContractType::symbol(e)']],
125
- ];
126
-
127
- for (const [fn, code] of overrides) {
128
- c.setFunctionCode(fn, code, fungibleTokenTrait);
129
- }
130
- }
131
-
132
104
  const functions = {
133
105
  ...fungibleFunctions,
134
106
  ...defineFunctions({
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Prints the `ContractType` associated type of a token trait, composed from the
3
+ * listed contract types with the library's `Compose` (e.g. `Compose<(AllowList, TotalSupply)>`).
4
+ */
5
+ export function printComposedContractType(contractTypes: string[]): string {
6
+ const list = contractTypes.length === 1 ? `${contractTypes[0]},` : contractTypes.join(', ');
7
+ return `type ContractType = Compose<(${list})>;`;
8
+ }
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * The actual latest version to use in links.
3
3
  */
4
- export const contractsVersion = '0.7.2';
4
+ export const contractsVersion = '0.8.0';
5
5
  export const contractsVersionTag = `v${contractsVersion}`;
6
6
 
7
7
  /**
8
8
  * Semantic version string representing of the minimum compatible version of Contracts to display in output.
9
9
  */
10
- export const compatibleContractsSemver = '^0.7.2';
10
+ export const compatibleContractsSemver = '^0.8.0';
11
11
 
12
12
  /**
13
13
  * The Soroban version for which compilation and testing have passing tests
14
14
  */
15
- export const compatibleSorobanVersion = '26.1.0';
15
+ export const compatibleSorobanVersion = '28.0.0';