@openzeppelin/wizard-stellar 0.4.5 → 0.6.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 (100) hide show
  1. package/dist/add-pausable.d.ts.map +1 -1
  2. package/dist/add-pausable.js +0 -2
  3. package/dist/add-pausable.js.map +1 -1
  4. package/dist/add-upgradeable.d.ts.map +1 -1
  5. package/dist/add-upgradeable.js +15 -14
  6. package/dist/add-upgradeable.js.map +1 -1
  7. package/dist/api.d.ts +5 -2
  8. package/dist/api.d.ts.map +1 -1
  9. package/dist/api.js +7 -1
  10. package/dist/api.js.map +1 -1
  11. package/dist/build-generic.d.ts +4 -0
  12. package/dist/build-generic.d.ts.map +1 -1
  13. package/dist/build-generic.js +3 -0
  14. package/dist/build-generic.js.map +1 -1
  15. package/dist/contract.d.ts +3 -0
  16. package/dist/contract.d.ts.map +1 -1
  17. package/dist/contract.js +4 -0
  18. package/dist/contract.js.map +1 -1
  19. package/dist/fungible.d.ts +1 -0
  20. package/dist/fungible.d.ts.map +1 -1
  21. package/dist/fungible.js +83 -40
  22. package/dist/fungible.js.map +1 -1
  23. package/dist/generate/fungible.d.ts.map +1 -1
  24. package/dist/generate/fungible.js +1 -0
  25. package/dist/generate/fungible.js.map +1 -1
  26. package/dist/generate/governor.d.ts +3 -0
  27. package/dist/generate/governor.d.ts.map +1 -0
  28. package/dist/generate/governor.js +24 -0
  29. package/dist/generate/governor.js.map +1 -0
  30. package/dist/generate/non-fungible.d.ts.map +1 -1
  31. package/dist/generate/non-fungible.js +1 -0
  32. package/dist/generate/non-fungible.js.map +1 -1
  33. package/dist/generate/sources.d.ts.map +1 -1
  34. package/dist/generate/sources.js +6 -0
  35. package/dist/generate/sources.js.map +1 -1
  36. package/dist/generate/stablecoin.d.ts.map +1 -1
  37. package/dist/generate/stablecoin.js +1 -0
  38. package/dist/generate/stablecoin.js.map +1 -1
  39. package/dist/governor.d.ts +17 -0
  40. package/dist/governor.d.ts.map +1 -0
  41. package/dist/governor.js +378 -0
  42. package/dist/governor.js.map +1 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -3
  46. package/dist/index.js.map +1 -1
  47. package/dist/kind.js +1 -0
  48. package/dist/kind.js.map +1 -1
  49. package/dist/non-fungible.d.ts +1 -0
  50. package/dist/non-fungible.d.ts.map +1 -1
  51. package/dist/non-fungible.js +72 -22
  52. package/dist/non-fungible.js.map +1 -1
  53. package/dist/print.d.ts.map +1 -1
  54. package/dist/print.js +6 -1
  55. package/dist/print.js.map +1 -1
  56. package/dist/set-access-control.d.ts.map +1 -1
  57. package/dist/set-access-control.js +16 -10
  58. package/dist/set-access-control.js.map +1 -1
  59. package/dist/stablecoin.d.ts.map +1 -1
  60. package/dist/stablecoin.js +7 -0
  61. package/dist/stablecoin.js.map +1 -1
  62. package/dist/test.js +3 -0
  63. package/dist/test.js.map +1 -1
  64. package/dist/utils/compile-test.d.ts +2 -2
  65. package/dist/utils/compile-test.d.ts.map +1 -1
  66. package/dist/utils/compile-test.js +54 -3
  67. package/dist/utils/compile-test.js.map +1 -1
  68. package/dist/utils/version.d.ts +4 -4
  69. package/dist/utils/version.js +3 -3
  70. package/dist/zip-rust.d.ts.map +1 -1
  71. package/dist/zip-rust.js +3 -2
  72. package/dist/zip-rust.js.map +1 -1
  73. package/dist/zip-shared.d.ts +3 -4
  74. package/dist/zip-shared.d.ts.map +1 -1
  75. package/dist/zip-shared.js +20 -7
  76. package/dist/zip-shared.js.map +1 -1
  77. package/package.json +1 -1
  78. package/src/add-pausable.ts +0 -1
  79. package/src/add-upgradeable.ts +15 -16
  80. package/src/api.ts +15 -2
  81. package/src/build-generic.ts +6 -0
  82. package/src/contract.ts +6 -0
  83. package/src/fungible.ts +102 -40
  84. package/src/generate/fungible.ts +1 -0
  85. package/src/generate/governor.ts +24 -0
  86. package/src/generate/non-fungible.ts +1 -0
  87. package/src/generate/sources.ts +7 -0
  88. package/src/generate/stablecoin.ts +1 -0
  89. package/src/governor.ts +425 -0
  90. package/src/index.ts +2 -0
  91. package/src/kind.ts +1 -0
  92. package/src/non-fungible.ts +77 -14
  93. package/src/print.ts +6 -1
  94. package/src/set-access-control.ts +16 -10
  95. package/src/stablecoin.ts +8 -0
  96. package/src/test.ts +4 -0
  97. package/src/utils/compile-test.ts +84 -5
  98. package/src/utils/version.ts +3 -3
  99. package/src/zip-rust.ts +4 -2
  100. package/src/zip-shared.ts +17 -6
@@ -0,0 +1,425 @@
1
+ import type { CommonContractOptions } from './common-options';
2
+ import { contractDefaults as commonDefaults, getSelfArg, withCommonContractDefaults } from './common-options';
3
+ import { ContractBuilder } from './contract';
4
+ import { printContract } from './print';
5
+ import { setAccessControl } from './set-access-control';
6
+ import { addUpgradeable } from './add-upgradeable';
7
+ import { setInfo } from './set-info';
8
+ import { toByteArray, toUint } from './utils/convert-strings';
9
+ import { defineFunctions } from './utils/define-functions';
10
+
11
+ export const defaults: Required<GovernorOptions> = {
12
+ name: 'MyGovernor',
13
+ version: '1.0.0',
14
+ votingDelay: '17000',
15
+ votingPeriod: '120000',
16
+ proposalThreshold: '100',
17
+ quorum: '500',
18
+ upgradeable: false,
19
+ timelock: false,
20
+ access: commonDefaults.access,
21
+ info: commonDefaults.info,
22
+ explicitImplementations: commonDefaults.explicitImplementations,
23
+ } as const;
24
+
25
+ export function printGovernor(opts: GovernorOptions = defaults): string {
26
+ return printContract(buildGovernor(opts));
27
+ }
28
+
29
+ export interface GovernorOptions extends CommonContractOptions {
30
+ name: string;
31
+ version?: string;
32
+ votingDelay?: string;
33
+ votingPeriod?: string;
34
+ proposalThreshold?: string;
35
+ quorum?: string;
36
+ upgradeable?: boolean;
37
+ timelock?: boolean;
38
+ }
39
+
40
+ export function isAccessControlRequired(opts: Partial<GovernorOptions>): boolean {
41
+ return opts.upgradeable === true;
42
+ }
43
+
44
+ function withDefaults(opts: GovernorOptions): Required<GovernorOptions> {
45
+ return {
46
+ ...opts,
47
+ ...withCommonContractDefaults(opts),
48
+ version: opts.version ?? defaults.version,
49
+ votingDelay: opts.votingDelay ?? defaults.votingDelay,
50
+ votingPeriod: opts.votingPeriod ?? defaults.votingPeriod,
51
+ proposalThreshold: opts.proposalThreshold ?? defaults.proposalThreshold,
52
+ quorum: opts.quorum ?? defaults.quorum,
53
+ upgradeable: opts.upgradeable ?? defaults.upgradeable,
54
+ timelock: opts.timelock ?? defaults.timelock,
55
+ };
56
+ }
57
+
58
+ export function buildGovernor(opts: GovernorOptions): ContractBuilder {
59
+ const allOpts = withDefaults(opts);
60
+ const c = new ContractBuilder(allOpts.name);
61
+
62
+ addBase(
63
+ c,
64
+ toByteArray(allOpts.name),
65
+ toByteArray(allOpts.version),
66
+ toUint(allOpts.votingDelay, 'votingDelay', 'u32'),
67
+ toUint(allOpts.votingPeriod, 'votingPeriod', 'u32'),
68
+ toUint(allOpts.proposalThreshold, 'proposalThreshold', 'u128'),
69
+ toUint(allOpts.quorum, 'quorum', 'u128'),
70
+ allOpts.timelock,
71
+ allOpts.explicitImplementations,
72
+ );
73
+
74
+ if (allOpts.upgradeable) {
75
+ addUpgradeable(c, allOpts.access, allOpts.explicitImplementations);
76
+ }
77
+
78
+ setAccessControl(c, allOpts.access, allOpts.explicitImplementations);
79
+ setInfo(c, allOpts.info);
80
+
81
+ return c;
82
+ }
83
+
84
+ function addBase(
85
+ c: ContractBuilder,
86
+ name: string,
87
+ version: string,
88
+ votingDelay: bigint,
89
+ votingPeriod: bigint,
90
+ proposalThreshold: bigint,
91
+ quorum: bigint,
92
+ timelock: boolean,
93
+ explicitImplementations: boolean,
94
+ ) {
95
+ c.addUseClause('stellar_governance::governor', 'Governor');
96
+ c.addUseClause('stellar_governance::governor', 'ProposalState');
97
+ c.addUseClause('stellar_governance::governor', 'self', { alias: 'governor' });
98
+ c.addUseClause('soroban_sdk', 'contract');
99
+ c.addUseClause('soroban_sdk', 'contractimpl');
100
+ c.addUseClause('soroban_sdk', 'Address');
101
+ c.addUseClause('soroban_sdk', 'BytesN');
102
+ c.addUseClause('soroban_sdk', 'Env');
103
+ c.addUseClause('soroban_sdk', 'String');
104
+ c.addUseClause('soroban_sdk', 'Symbol');
105
+ c.addUseClause('soroban_sdk', 'Val');
106
+ c.addUseClause('soroban_sdk', 'Vec');
107
+
108
+ c.addConstructorArgument({ name: 'token_contract', type: 'Address' });
109
+ c.addConstructorCode(`governor::set_name(e, String::from_str(e, "${name}"));`);
110
+ c.addConstructorCode(`governor::set_version(e, String::from_str(e, "${version}"));`);
111
+ c.addConstructorCode(`governor::set_voting_delay(e, ${votingDelay});`);
112
+ c.addConstructorCode(`governor::set_voting_period(e, ${votingPeriod});`);
113
+ c.addConstructorCode(`governor::set_proposal_threshold(e, ${proposalThreshold});`);
114
+ c.addConstructorCode(`governor::set_quorum(e, ${quorum});`);
115
+ c.addConstructorCode('governor::set_token_contract(e, &token_contract);');
116
+
117
+ const governorTrait = {
118
+ traitName: 'Governor',
119
+ structName: c.name,
120
+ tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
121
+ };
122
+
123
+ // Custom impls must be added before the defaults so the deduplication keeps
124
+ // the custom version when a default-method override exists (e.g. timelock's
125
+ // `proposals_need_queuing`/`queue` overriding the trait defaults).
126
+ if (timelock) {
127
+ addTimelock(c, governorTrait);
128
+ } else {
129
+ c.addTraitFunction(governorTrait, functions.execute);
130
+ c.addTraitFunction(governorTrait, functions.cancel);
131
+ }
132
+
133
+ if (explicitImplementations) {
134
+ c.addTraitForEachFunctions(governorTrait, governorTraitDefaultFunctions);
135
+ }
136
+ }
137
+
138
+ function addTimelock(c: ContractBuilder, governorTrait: { traitName: string; structName: string; tags: string[] }) {
139
+ c.addUseClause('soroban_sdk', 'contracttype');
140
+ c.addUseClause('soroban_sdk', 'IntoVal');
141
+ c.addUseClause('stellar_governance::timelock', 'TimelockClient');
142
+
143
+ c.addTopLevelDeclaration([
144
+ '#[contracttype]',
145
+ 'enum GovernorTimelockKey {',
146
+ ' Timelock,',
147
+ ' OperationID(BytesN<32>),',
148
+ '}',
149
+ ]);
150
+
151
+ c.addConstructorArgument({ name: 'timelock_contract', type: 'Address' });
152
+ c.addConstructorCode('e.storage().instance().set(&GovernorTimelockKey::Timelock, &timelock_contract);');
153
+
154
+ c.addFreeFunction(timelockFreeFunctions.timelock);
155
+
156
+ c.addTraitFunction(governorTrait, timelockFunctions.proposals_need_queuing);
157
+ c.addTraitFunction(governorTrait, timelockFunctions.queue);
158
+ c.addTraitFunction(governorTrait, timelockFunctions.execute);
159
+ c.addTraitFunction(governorTrait, timelockFunctions.cancel);
160
+ }
161
+
162
+ const functions = defineFunctions({
163
+ execute: {
164
+ args: [
165
+ getSelfArg(),
166
+ { name: 'targets', type: 'Vec<Address>' },
167
+ { name: 'functions', type: 'Vec<Symbol>' },
168
+ { name: 'args', type: 'Vec<Vec<Val>>' },
169
+ { name: 'description_hash', type: 'BytesN<32>' },
170
+ { name: 'executor', type: 'Address' },
171
+ ],
172
+ returns: 'BytesN<32>',
173
+ code: [
174
+ 'executor.require_auth()',
175
+ 'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
176
+ 'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
177
+ 'let quorum = Self::quorum(e, snapshot)',
178
+ 'governor::execute(e, targets, functions, args, &description_hash, Self::proposals_need_queuing(e), quorum)',
179
+ ],
180
+ },
181
+ cancel: {
182
+ args: [
183
+ getSelfArg(),
184
+ { name: 'targets', type: 'Vec<Address>' },
185
+ { name: 'functions', type: 'Vec<Symbol>' },
186
+ { name: 'args', type: 'Vec<Vec<Val>>' },
187
+ { name: 'description_hash', type: 'BytesN<32>' },
188
+ { name: 'operator', type: 'Address' },
189
+ ],
190
+ returns: 'BytesN<32>',
191
+ code: [
192
+ 'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
193
+ 'let proposer = governor::get_proposal_proposer(e, &proposal_id)',
194
+ 'assert!(operator == proposer)',
195
+ 'operator.require_auth()',
196
+ 'governor::cancel(e, targets, functions, args, &description_hash)',
197
+ ],
198
+ },
199
+ });
200
+
201
+ const timelockFreeFunctions = defineFunctions({
202
+ timelock: {
203
+ args: [getSelfArg()],
204
+ returns: 'Address',
205
+ code: ['e.storage().instance().get(&GovernorTimelockKey::Timelock).expect("timelock not set")'],
206
+ },
207
+ });
208
+
209
+ const timelockFunctions = defineFunctions({
210
+ proposals_need_queuing: {
211
+ args: [{ name: '_e', type: '&Env' }],
212
+ returns: 'bool',
213
+ code: ['true'],
214
+ },
215
+ queue: {
216
+ args: [
217
+ getSelfArg(),
218
+ { name: 'targets', type: 'Vec<Address>' },
219
+ { name: 'functions', type: 'Vec<Symbol>' },
220
+ { name: 'args', type: 'Vec<Vec<Val>>' },
221
+ { name: 'description_hash', type: 'BytesN<32>' },
222
+ { name: 'eta', type: 'u32' },
223
+ { name: '_operator', type: 'Address' },
224
+ ],
225
+ returns: 'BytesN<32>',
226
+ code: [
227
+ 'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
228
+ 'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
229
+ 'let quorum = Self::quorum(e, snapshot)',
230
+ 'let proposal_id = governor::queue(e, targets.clone(), functions.clone(), args.clone(), &description_hash, eta, quorum)',
231
+ 'let timelock = Self::timelock(e)',
232
+ 'let delay = eta.saturating_sub(e.ledger().sequence())',
233
+ 'let execute_args: Vec<Val> = (targets, functions, args, description_hash.clone(), timelock.clone()).into_val(e)',
234
+ 'let timelock_client = TimelockClient::new(e, &timelock)',
235
+ 'let zero_predecessor = BytesN::from_array(e, &[0u8; 32])',
236
+ 'let op_id = timelock_client.schedule(&e.current_contract_address(), &Symbol::new(e, "execute"), &execute_args, &zero_predecessor, &description_hash, &delay, &e.current_contract_address())',
237
+ 'e.storage().persistent().set(&GovernorTimelockKey::OperationID(proposal_id.clone()), &op_id);',
238
+ 'proposal_id',
239
+ ],
240
+ },
241
+ execute: {
242
+ args: [
243
+ getSelfArg(),
244
+ { name: 'targets', type: 'Vec<Address>' },
245
+ { name: 'functions', type: 'Vec<Symbol>' },
246
+ { name: 'args', type: 'Vec<Vec<Val>>' },
247
+ { name: 'description_hash', type: 'BytesN<32>' },
248
+ { name: 'executor', type: 'Address' },
249
+ ],
250
+ returns: 'BytesN<32>',
251
+ code: [
252
+ 'let timelock = Self::timelock(e)',
253
+ 'assert!(executor == timelock)',
254
+ 'executor.require_auth()',
255
+ 'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
256
+ 'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
257
+ 'let quorum = Self::quorum(e, snapshot)',
258
+ 'governor::execute(e, targets, functions, args, &description_hash, Self::proposals_need_queuing(e), quorum)',
259
+ ],
260
+ },
261
+ cancel: {
262
+ args: [
263
+ getSelfArg(),
264
+ { name: 'targets', type: 'Vec<Address>' },
265
+ { name: 'functions', type: 'Vec<Symbol>' },
266
+ { name: 'args', type: 'Vec<Vec<Val>>' },
267
+ { name: 'description_hash', type: 'BytesN<32>' },
268
+ { name: 'operator', type: 'Address' },
269
+ ],
270
+ returns: 'BytesN<32>',
271
+ code: [
272
+ 'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
273
+ 'let proposer = governor::get_proposal_proposer(e, &proposal_id)',
274
+ 'assert!(operator == proposer)',
275
+ 'operator.require_auth()',
276
+ 'if let Some(op_id) = e.storage().persistent().get::<_, BytesN<32>>(&GovernorTimelockKey::OperationID(proposal_id)) {',
277
+ ' let timelock = Self::timelock(e);',
278
+ ' let timelock_client = TimelockClient::new(e, &timelock);',
279
+ ' timelock_client.cancel(&op_id, &e.current_contract_address());',
280
+ '}',
281
+ 'governor::cancel(e, targets, functions, args, &description_hash)',
282
+ ],
283
+ },
284
+ });
285
+
286
+ // Explicit-trait variants for every default method on the `Governor` trait.
287
+ // When `explicitImplementations` is enabled, these are emitted as overrides
288
+ // that mirror the upstream defaults — letting users see and customize each
289
+ // method directly. Order matches the trait definition for readability.
290
+ const governorTraitDefaultFunctions = defineFunctions({
291
+ name: {
292
+ args: [getSelfArg()],
293
+ returns: 'String',
294
+ code: ['governor::get_name(e)'],
295
+ },
296
+ version: {
297
+ args: [getSelfArg()],
298
+ returns: 'String',
299
+ code: ['governor::get_version(e)'],
300
+ },
301
+ voting_delay: {
302
+ args: [getSelfArg()],
303
+ returns: 'u32',
304
+ code: ['governor::get_voting_delay(e)'],
305
+ },
306
+ voting_period: {
307
+ args: [getSelfArg()],
308
+ returns: 'u32',
309
+ code: ['governor::get_voting_period(e)'],
310
+ },
311
+ proposal_threshold: {
312
+ args: [getSelfArg()],
313
+ returns: 'u128',
314
+ code: ['governor::get_proposal_threshold(e)'],
315
+ },
316
+ get_token_contract: {
317
+ args: [getSelfArg()],
318
+ returns: 'Address',
319
+ code: ['governor::get_token_contract(e)'],
320
+ },
321
+ counting_mode: {
322
+ args: [getSelfArg()],
323
+ returns: 'Symbol',
324
+ code: ['governor::counting_mode(e)'],
325
+ },
326
+ has_voted: {
327
+ args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }, { name: 'account', type: 'Address' }],
328
+ returns: 'bool',
329
+ code: ['governor::has_voted(e, &proposal_id, &account)'],
330
+ },
331
+ quorum: {
332
+ args: [getSelfArg(), { name: 'ledger', type: 'u32' }],
333
+ returns: 'u128',
334
+ code: ['governor::get_quorum(e, ledger)'],
335
+ },
336
+ proposal_state: {
337
+ args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
338
+ returns: 'ProposalState',
339
+ code: [
340
+ 'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
341
+ 'let quorum = Self::quorum(e, snapshot)',
342
+ 'governor::get_proposal_state(e, &proposal_id, quorum)',
343
+ ],
344
+ },
345
+ proposal_snapshot: {
346
+ args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
347
+ returns: 'u32',
348
+ code: ['governor::get_proposal_snapshot(e, &proposal_id)'],
349
+ },
350
+ proposal_deadline: {
351
+ args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
352
+ returns: 'u32',
353
+ code: ['governor::get_proposal_deadline(e, &proposal_id)'],
354
+ },
355
+ proposal_proposer: {
356
+ args: [getSelfArg(), { name: 'proposal_id', type: 'BytesN<32>' }],
357
+ returns: 'Address',
358
+ code: ['governor::get_proposal_proposer(e, &proposal_id)'],
359
+ },
360
+ get_proposal_id: {
361
+ args: [
362
+ getSelfArg(),
363
+ { name: 'targets', type: 'Vec<Address>' },
364
+ { name: 'functions', type: 'Vec<Symbol>' },
365
+ { name: 'args', type: 'Vec<Vec<Val>>' },
366
+ { name: 'description_hash', type: 'BytesN<32>' },
367
+ ],
368
+ returns: 'BytesN<32>',
369
+ code: ['governor::hash_proposal(e, &targets, &functions, &args, &description_hash)'],
370
+ },
371
+ propose: {
372
+ args: [
373
+ getSelfArg(),
374
+ { name: 'targets', type: 'Vec<Address>' },
375
+ { name: 'functions', type: 'Vec<Symbol>' },
376
+ { name: 'args', type: 'Vec<Vec<Val>>' },
377
+ { name: 'description', type: 'String' },
378
+ { name: 'proposer', type: 'Address' },
379
+ ],
380
+ returns: 'BytesN<32>',
381
+ code: ['proposer.require_auth()', 'governor::propose(e, targets, functions, args, description, &proposer)'],
382
+ },
383
+ cast_vote: {
384
+ args: [
385
+ getSelfArg(),
386
+ { name: 'proposal_id', type: 'BytesN<32>' },
387
+ { name: 'vote_type', type: 'u32' },
388
+ { name: 'reason', type: 'String' },
389
+ { name: 'voter', type: 'Address' },
390
+ ],
391
+ returns: 'u128',
392
+ code: [
393
+ 'voter.require_auth()',
394
+ 'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
395
+ 'let quorum = Self::quorum(e, snapshot)',
396
+ 'governor::cast_vote(e, &proposal_id, vote_type, &reason, &voter, quorum)',
397
+ ],
398
+ },
399
+ queue: {
400
+ args: [
401
+ getSelfArg(),
402
+ { name: 'targets', type: 'Vec<Address>' },
403
+ { name: 'functions', type: 'Vec<Symbol>' },
404
+ { name: 'args', type: 'Vec<Vec<Val>>' },
405
+ { name: 'description_hash', type: 'BytesN<32>' },
406
+ { name: 'eta', type: 'u32' },
407
+ { name: '_operator', type: 'Address' },
408
+ ],
409
+ returns: 'BytesN<32>',
410
+ code: [
411
+ 'if !Self::proposals_need_queuing(e) {',
412
+ ' soroban_sdk::panic_with_error!(e, governor::GovernorError::QueueNotEnabled);',
413
+ '}',
414
+ 'let proposal_id = governor::hash_proposal(e, &targets, &functions, &args, &description_hash)',
415
+ 'let snapshot = governor::get_proposal_snapshot(e, &proposal_id)',
416
+ 'let quorum = Self::quorum(e, snapshot)',
417
+ 'governor::queue(e, targets, functions, args, &description_hash, eta, quorum)',
418
+ ],
419
+ },
420
+ proposals_need_queuing: {
421
+ args: [{ name: '_e', type: '&Env' }],
422
+ returns: 'bool',
423
+ code: ['false'],
424
+ },
425
+ });
package/src/index.ts CHANGED
@@ -22,9 +22,11 @@ export { sanitizeKind } from './kind';
22
22
  export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version';
23
23
 
24
24
  export { fungible } from './api';
25
+ export { governor } from './api';
25
26
  export { nonFungible } from './api';
26
27
  export { stablecoin } from './api';
27
28
 
28
29
  export type { FungibleOptions } from './fungible';
30
+ export type { GovernorOptions } from './governor';
29
31
  export type { NonFungibleOptions } from './non-fungible';
30
32
  export type { StablecoinOptions } from './stablecoin';
package/src/kind.ts CHANGED
@@ -15,6 +15,7 @@ export function sanitizeKind(kind: unknown): Kind {
15
15
  function isKind<T>(value: Kind | T): value is Kind {
16
16
  switch (value) {
17
17
  case 'Fungible':
18
+ case 'Governor':
18
19
  case 'NonFungible':
19
20
  case 'Stablecoin':
20
21
  return true;
@@ -18,6 +18,7 @@ export const defaults: Required<NonFungibleOptions> = {
18
18
  symbol: 'MTK',
19
19
  tokenUri: 'https://www.mytoken.com',
20
20
  burnable: false,
21
+ votes: false,
21
22
  enumerable: false,
22
23
  consecutive: false,
23
24
  pausable: false,
@@ -38,6 +39,7 @@ export interface NonFungibleOptions extends CommonContractOptions {
38
39
  symbol: string;
39
40
  tokenUri?: string;
40
41
  burnable?: boolean;
42
+ votes?: boolean;
41
43
  enumerable?: boolean;
42
44
  consecutive?: boolean;
43
45
  pausable?: boolean;
@@ -52,6 +54,7 @@ function withDefaults(opts: NonFungibleOptions): Required<NonFungibleOptions> {
52
54
  ...withCommonContractDefaults(opts),
53
55
  tokenUri: opts.tokenUri ?? defaults.tokenUri,
54
56
  burnable: opts.burnable ?? defaults.burnable,
57
+ votes: opts.votes ?? defaults.votes,
55
58
  consecutive: opts.consecutive ?? defaults.consecutive,
56
59
  enumerable: opts.enumerable ?? defaults.enumerable,
57
60
  pausable: opts.pausable ?? defaults.pausable,
@@ -87,6 +90,16 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
87
90
  errors.sequential = 'Sequential minting cannot be used with Consecutive extension';
88
91
  }
89
92
 
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
+
90
103
  if (Object.keys(errors).length > 0) {
91
104
  throw new OptionsError(errors);
92
105
  }
@@ -96,6 +109,7 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
96
109
  toByteArray(allOpts.name),
97
110
  toByteArray(allOpts.symbol),
98
111
  toByteArray(allOpts.tokenUri),
112
+ allOpts.votes,
99
113
  allOpts.pausable,
100
114
  allOpts.explicitImplementations,
101
115
  );
@@ -124,6 +138,7 @@ export function buildNonFungible(opts: NonFungibleOptions): Contract {
124
138
  addMintable(
125
139
  c,
126
140
  allOpts.enumerable,
141
+ allOpts.votes,
127
142
  allOpts.pausable,
128
143
  allOpts.sequential,
129
144
  allOpts.access,
@@ -142,6 +157,7 @@ function addBase(
142
157
  name: string,
143
158
  symbol: string,
144
159
  tokenUri: string,
160
+ votes: boolean,
145
161
  pausable: boolean,
146
162
  explicitImplementations: boolean,
147
163
  ) {
@@ -154,19 +170,22 @@ function addBase(
154
170
  // Set token functions
155
171
  c.addUseClause('stellar_tokens::non_fungible', 'Base');
156
172
  c.addUseClause('stellar_tokens::non_fungible', 'NonFungibleToken');
173
+ if (votes) {
174
+ c.addUseClause('stellar_governance::votes', 'Votes');
175
+ c.addUseClause('stellar_tokens::non_fungible', 'votes::NonFungibleVotes');
176
+ }
157
177
  if (explicitImplementations) c.addUseClause('stellar_tokens::non_fungible', 'ContractOverrides');
158
- else c.addUseClause('stellar_macros', 'default_impl');
159
178
  c.addUseClause('soroban_sdk', 'contract');
160
179
  c.addUseClause('soroban_sdk', 'contractimpl');
161
180
  c.addUseClause('soroban_sdk', 'String');
162
181
  c.addUseClause('soroban_sdk', 'Env');
163
- if (explicitImplementations || pausable) c.addUseClause('soroban_sdk', 'Address');
182
+ c.addUseClause('soroban_sdk', 'Address');
164
183
 
165
184
  const nonFungibleTokenTrait = {
166
185
  traitName: 'NonFungibleToken',
167
186
  structName: c.name,
168
- tags: explicitImplementations ? ['contractimpl'] : ['default_impl', 'contractimpl'],
169
- assocType: 'type ContractType = Base;',
187
+ tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
188
+ assocType: `type ContractType = ${votes ? 'NonFungibleVotes' : 'Base'};`,
170
189
  };
171
190
 
172
191
  c.addTraitImplBlock(nonFungibleTokenTrait);
@@ -182,6 +201,15 @@ function addBase(
182
201
  c.addFunctionTag(baseFunctions.transfer_from, 'when_not_paused', nonFungibleTokenTrait);
183
202
  c.addTraitFunction(nonFungibleTokenTrait, baseFunctions.transfer_from);
184
203
  }
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
+ }
185
213
  }
186
214
 
187
215
  function addBurnable(c: ContractBuilder, pausable: boolean, explicitImplementations: boolean) {
@@ -190,7 +218,7 @@ function addBurnable(c: ContractBuilder, pausable: boolean, explicitImplementati
190
218
  const nonFungibleBurnableTrait = {
191
219
  traitName: 'NonFungibleBurnable',
192
220
  structName: c.name,
193
- tags: ['contractimpl'],
221
+ tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
194
222
  section: 'Extensions',
195
223
  };
196
224
 
@@ -205,21 +233,17 @@ function addBurnable(c: ContractBuilder, pausable: boolean, explicitImplementati
205
233
  } else if (explicitImplementations)
206
234
  c.addTraitForEachFunctions(nonFungibleBurnableTrait, nonFungibleBurnableFunctions);
207
235
  else {
208
- // prepend '#[default_impl]'
209
- nonFungibleBurnableTrait.tags.unshift('default_impl');
210
236
  c.addTraitImplBlock(nonFungibleBurnableTrait);
211
237
  }
212
238
  }
213
239
 
214
240
  function addEnumerable(c: ContractBuilder, explicitImplementations: boolean) {
215
241
  c.addUseClause('stellar_tokens::non_fungible', 'enumerable::{NonFungibleEnumerable, Enumerable}');
216
- if (explicitImplementations) c.addUseClause('soroban_sdk', 'Address');
217
- else c.addUseClause('stellar_macros', 'default_impl');
218
242
 
219
243
  const nonFungibleEnumerableTrait = {
220
244
  traitName: 'NonFungibleEnumerable',
221
245
  structName: c.name,
222
- tags: explicitImplementations ? ['contractimpl'] : ['default_impl', 'contractimpl'],
246
+ tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
223
247
  section: 'Extensions',
224
248
  };
225
249
  if (explicitImplementations)
@@ -231,7 +255,6 @@ function addEnumerable(c: ContractBuilder, explicitImplementations: boolean) {
231
255
 
232
256
  function addConsecutive(c: ContractBuilder, pausable: boolean, access: Access, explicitImplementations: boolean) {
233
257
  c.addUseClause('stellar_tokens::non_fungible', 'consecutive::{NonFungibleConsecutive, Consecutive}');
234
- c.addUseClause('soroban_sdk', 'Address');
235
258
 
236
259
  const effectiveAccess = access === false ? DEFAULT_ACCESS_CONTROL : access;
237
260
  const nonFungibleConsecutiveTrait = {
@@ -269,12 +292,12 @@ function addConsecutive(c: ContractBuilder, pausable: boolean, access: Access, e
269
292
  function addMintable(
270
293
  c: ContractBuilder,
271
294
  enumerable: boolean,
295
+ votes: boolean,
272
296
  pausable: boolean,
273
297
  sequential: boolean,
274
298
  access: Access,
275
299
  explicitImplementations: boolean,
276
300
  ) {
277
- c.addUseClause('soroban_sdk', 'Address');
278
301
  const accessProps = { useMacro: true, role: 'minter', caller: 'caller' };
279
302
  const effectiveAccess = access === false ? DEFAULT_ACCESS_CONTROL : access;
280
303
 
@@ -295,9 +318,22 @@ function addMintable(
295
318
  } else {
296
319
  if (sequential) {
297
320
  mintFn =
298
- effectiveAccess === 'ownable' ? baseFunctions.sequential_mint : baseFunctions.sequential_mint_with_caller;
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;
299
328
  } else {
300
- mintFn = effectiveAccess === 'ownable' ? baseFunctions.mint : baseFunctions.mint_with_caller;
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;
301
337
  }
302
338
  }
303
339
 
@@ -412,6 +448,33 @@ const baseFunctions = defineFunctions({
412
448
  },
413
449
  });
414
450
 
451
+ const votesMintFunctions = defineFunctions({
452
+ mint: {
453
+ args: [getSelfArg(), { name: 'to', type: 'Address' }, { name: 'token_id', type: 'u32' }],
454
+ code: ['NonFungibleVotes::mint(e, &to, token_id);'],
455
+ },
456
+ mint_with_caller: {
457
+ name: 'mint',
458
+ args: [
459
+ getSelfArg(),
460
+ { name: 'to', type: 'Address' },
461
+ { name: 'token_id', type: 'u32' },
462
+ { name: 'caller', type: 'Address' },
463
+ ],
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);'],
475
+ },
476
+ });
477
+
415
478
  const nonFungibleTokenTraitFunctions = pickKeys(baseFunctions, [
416
479
  'balance',
417
480
  'owner_of',
package/src/print.ts CHANGED
@@ -4,7 +4,6 @@ import type { Lines } from './utils/format-lines';
4
4
  import { formatLines, spaceBetween } from './utils/format-lines';
5
5
  import { getSelfArg } from './common-options';
6
6
  import { compatibleContractsSemver } from './utils/version';
7
- import { toByteArray } from './utils/convert-strings';
8
7
 
9
8
  const DEFAULT_SECTION = '1. with no section';
10
9
  const STANDALONE_IMPORTS_GROUP = 'Standalone Imports';
@@ -31,6 +30,7 @@ export function printContract(contract: Contract): string {
31
30
  printUseClauses(contract),
32
31
  printMetadata(contract),
33
32
  printVariables(contract),
33
+ printTopLevelDeclarations(contract),
34
34
  printContractStruct(contract),
35
35
  printContractErrors(contract),
36
36
  printContractFunctions(contract),
@@ -364,6 +364,11 @@ function printArgument(arg: Argument): string {
364
364
  }
365
365
  }
366
366
 
367
+ function printTopLevelDeclarations(contract: Contract): Lines[] {
368
+ if (contract.topLevelDeclarations.length === 0) return [];
369
+ return spaceBetween(...contract.topLevelDeclarations);
370
+ }
371
+
367
372
  function printDocumentations(documentations: string[]): string[] {
368
373
  return documentations.map(documentation => `//! ${documentation}`);
369
374
  }