@openzeppelin/wizard-stellar 0.5.0 → 0.6.1

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 (92) hide show
  1. package/dist/add-upgradeable.d.ts.map +1 -1
  2. package/dist/add-upgradeable.js +15 -14
  3. package/dist/add-upgradeable.js.map +1 -1
  4. package/dist/api.d.ts +5 -2
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/api.js +7 -1
  7. package/dist/api.js.map +1 -1
  8. package/dist/build-generic.d.ts +4 -0
  9. package/dist/build-generic.d.ts.map +1 -1
  10. package/dist/build-generic.js +3 -0
  11. package/dist/build-generic.js.map +1 -1
  12. package/dist/contract.d.ts +3 -0
  13. package/dist/contract.d.ts.map +1 -1
  14. package/dist/contract.js +4 -0
  15. package/dist/contract.js.map +1 -1
  16. package/dist/fungible.d.ts +3 -1
  17. package/dist/fungible.d.ts.map +1 -1
  18. package/dist/fungible.js +80 -27
  19. package/dist/fungible.js.map +1 -1
  20. package/dist/generate/fungible.d.ts.map +1 -1
  21. package/dist/generate/fungible.js +2 -0
  22. package/dist/generate/fungible.js.map +1 -1
  23. package/dist/generate/governor.d.ts +3 -0
  24. package/dist/generate/governor.d.ts.map +1 -0
  25. package/dist/generate/governor.js +24 -0
  26. package/dist/generate/governor.js.map +1 -0
  27. package/dist/generate/non-fungible.d.ts.map +1 -1
  28. package/dist/generate/non-fungible.js +1 -0
  29. package/dist/generate/non-fungible.js.map +1 -1
  30. package/dist/generate/sources.d.ts.map +1 -1
  31. package/dist/generate/sources.js +6 -0
  32. package/dist/generate/sources.js.map +1 -1
  33. package/dist/generate/stablecoin.d.ts.map +1 -1
  34. package/dist/generate/stablecoin.js +2 -0
  35. package/dist/generate/stablecoin.js.map +1 -1
  36. package/dist/governor.d.ts +17 -0
  37. package/dist/governor.d.ts.map +1 -0
  38. package/dist/governor.js +378 -0
  39. package/dist/governor.js.map +1 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +5 -3
  43. package/dist/index.js.map +1 -1
  44. package/dist/kind.js +1 -0
  45. package/dist/kind.js.map +1 -1
  46. package/dist/non-fungible.d.ts +1 -0
  47. package/dist/non-fungible.d.ts.map +1 -1
  48. package/dist/non-fungible.js +68 -7
  49. package/dist/non-fungible.js.map +1 -1
  50. package/dist/print.d.ts.map +1 -1
  51. package/dist/print.js +6 -1
  52. package/dist/print.js.map +1 -1
  53. package/dist/stablecoin.d.ts.map +1 -1
  54. package/dist/stablecoin.js +7 -0
  55. package/dist/stablecoin.js.map +1 -1
  56. package/dist/test.js +3 -0
  57. package/dist/test.js.map +1 -1
  58. package/dist/utils/compile-test.d.ts +2 -2
  59. package/dist/utils/compile-test.d.ts.map +1 -1
  60. package/dist/utils/compile-test.js +54 -3
  61. package/dist/utils/compile-test.js.map +1 -1
  62. package/dist/utils/version.d.ts +4 -4
  63. package/dist/utils/version.js +3 -3
  64. package/dist/zip-rust.d.ts.map +1 -1
  65. package/dist/zip-rust.js +3 -2
  66. package/dist/zip-rust.js.map +1 -1
  67. package/dist/zip-shared.d.ts +3 -4
  68. package/dist/zip-shared.d.ts.map +1 -1
  69. package/dist/zip-shared.js +20 -7
  70. package/dist/zip-shared.js.map +1 -1
  71. package/package.json +1 -1
  72. package/src/add-upgradeable.ts +15 -16
  73. package/src/api.ts +15 -2
  74. package/src/build-generic.ts +6 -0
  75. package/src/contract.ts +6 -0
  76. package/src/fungible.ts +106 -27
  77. package/src/generate/fungible.ts +2 -0
  78. package/src/generate/governor.ts +24 -0
  79. package/src/generate/non-fungible.ts +1 -0
  80. package/src/generate/sources.ts +7 -0
  81. package/src/generate/stablecoin.ts +2 -0
  82. package/src/governor.ts +425 -0
  83. package/src/index.ts +2 -0
  84. package/src/kind.ts +1 -0
  85. package/src/non-fungible.ts +73 -3
  86. package/src/print.ts +6 -1
  87. package/src/stablecoin.ts +8 -0
  88. package/src/test.ts +4 -0
  89. package/src/utils/compile-test.ts +84 -5
  90. package/src/utils/version.ts +3 -3
  91. package/src/zip-rust.ts +4 -2
  92. 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,6 +170,10 @@ 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
178
  c.addUseClause('soroban_sdk', 'contract');
159
179
  c.addUseClause('soroban_sdk', 'contractimpl');
@@ -165,7 +185,7 @@ function addBase(
165
185
  traitName: 'NonFungibleToken',
166
186
  structName: c.name,
167
187
  tags: explicitImplementations ? ['contractimpl'] : ['contractimpl(contracttrait)'],
168
- assocType: 'type ContractType = Base;',
188
+ assocType: `type ContractType = ${votes ? 'NonFungibleVotes' : 'Base'};`,
169
189
  };
170
190
 
171
191
  c.addTraitImplBlock(nonFungibleTokenTrait);
@@ -181,6 +201,15 @@ function addBase(
181
201
  c.addFunctionTag(baseFunctions.transfer_from, 'when_not_paused', nonFungibleTokenTrait);
182
202
  c.addTraitFunction(nonFungibleTokenTrait, baseFunctions.transfer_from);
183
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
+ }
184
213
  }
185
214
 
186
215
  function addBurnable(c: ContractBuilder, pausable: boolean, explicitImplementations: boolean) {
@@ -263,6 +292,7 @@ function addConsecutive(c: ContractBuilder, pausable: boolean, access: Access, e
263
292
  function addMintable(
264
293
  c: ContractBuilder,
265
294
  enumerable: boolean,
295
+ votes: boolean,
266
296
  pausable: boolean,
267
297
  sequential: boolean,
268
298
  access: Access,
@@ -288,9 +318,22 @@ function addMintable(
288
318
  } else {
289
319
  if (sequential) {
290
320
  mintFn =
291
- 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;
292
328
  } else {
293
- 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;
294
337
  }
295
338
  }
296
339
 
@@ -405,6 +448,33 @@ const baseFunctions = defineFunctions({
405
448
  },
406
449
  });
407
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
+
408
478
  const nonFungibleTokenTraitFunctions = pickKeys(baseFunctions, [
409
479
  'balance',
410
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
  }
package/src/stablecoin.ts CHANGED
@@ -11,6 +11,7 @@ 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';
14
15
 
15
16
  export const defaults: Required<StablecoinOptions> = {
16
17
  ...fungibleDefaults,
@@ -46,6 +47,13 @@ export function isAccessControlRequired(opts: Partial<StablecoinOptions>): boole
46
47
  export function buildStablecoin(opts: StablecoinOptions): Contract {
47
48
  const allOpts = withDefaults(opts);
48
49
 
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
+
49
57
  const c = buildFungible(allOpts);
50
58
 
51
59
  if (allOpts.limitations) {
package/src/test.ts CHANGED
@@ -17,6 +17,10 @@ test.serial('fungible result generated', async t => {
17
17
  await testGenerate(t, 'Fungible');
18
18
  });
19
19
 
20
+ test.serial('governor result generated', async t => {
21
+ await testGenerate(t, 'Governor');
22
+ });
23
+
20
24
  test.serial('non-fungible result generated', async t => {
21
25
  await testGenerate(t, 'NonFungible');
22
26
  });