@silvana-one/token 0.1.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 (68) hide show
  1. package/README.md +1 -0
  2. package/dist/node/BondingCurveAdmin.d.ts +690 -0
  3. package/dist/node/BondingCurveAdmin.js +504 -0
  4. package/dist/node/BondingCurveAdmin.js.map +1 -0
  5. package/dist/node/FungibleToken.d.ts +414 -0
  6. package/dist/node/FungibleToken.js +7 -0
  7. package/dist/node/FungibleToken.js.map +1 -0
  8. package/dist/node/FungibleTokenAdvancedAdmin.d.ts +124 -0
  9. package/dist/node/FungibleTokenAdvancedAdmin.js +226 -0
  10. package/dist/node/FungibleTokenAdvancedAdmin.js.map +1 -0
  11. package/dist/node/FungibleTokenContract.d.ts +526 -0
  12. package/dist/node/FungibleTokenContract.js +295 -0
  13. package/dist/node/FungibleTokenContract.js.map +1 -0
  14. package/dist/node/FungibleTokenStandardAdmin.d.ts +27 -0
  15. package/dist/node/FungibleTokenStandardAdmin.js +101 -0
  16. package/dist/node/FungibleTokenStandardAdmin.js.map +1 -0
  17. package/dist/node/bid.d.ts +86 -0
  18. package/dist/node/bid.js +168 -0
  19. package/dist/node/bid.js.map +1 -0
  20. package/dist/node/claim.d.ts +89 -0
  21. package/dist/node/claim.js +156 -0
  22. package/dist/node/claim.js.map +1 -0
  23. package/dist/node/index.cjs +1576 -0
  24. package/dist/node/index.d.ts +8 -0
  25. package/dist/node/index.js +9 -0
  26. package/dist/node/index.js.map +1 -0
  27. package/dist/node/offer.d.ts +87 -0
  28. package/dist/node/offer.js +169 -0
  29. package/dist/node/offer.js.map +1 -0
  30. package/dist/tsconfig.tsbuildinfo +1 -0
  31. package/dist/tsconfig.web.tsbuildinfo +1 -0
  32. package/dist/web/BondingCurveAdmin.d.ts +690 -0
  33. package/dist/web/BondingCurveAdmin.js +504 -0
  34. package/dist/web/BondingCurveAdmin.js.map +1 -0
  35. package/dist/web/FungibleToken.d.ts +414 -0
  36. package/dist/web/FungibleToken.js +7 -0
  37. package/dist/web/FungibleToken.js.map +1 -0
  38. package/dist/web/FungibleTokenAdvancedAdmin.d.ts +124 -0
  39. package/dist/web/FungibleTokenAdvancedAdmin.js +226 -0
  40. package/dist/web/FungibleTokenAdvancedAdmin.js.map +1 -0
  41. package/dist/web/FungibleTokenContract.d.ts +526 -0
  42. package/dist/web/FungibleTokenContract.js +295 -0
  43. package/dist/web/FungibleTokenContract.js.map +1 -0
  44. package/dist/web/FungibleTokenStandardAdmin.d.ts +27 -0
  45. package/dist/web/FungibleTokenStandardAdmin.js +101 -0
  46. package/dist/web/FungibleTokenStandardAdmin.js.map +1 -0
  47. package/dist/web/bid.d.ts +86 -0
  48. package/dist/web/bid.js +168 -0
  49. package/dist/web/bid.js.map +1 -0
  50. package/dist/web/claim.d.ts +89 -0
  51. package/dist/web/claim.js +156 -0
  52. package/dist/web/claim.js.map +1 -0
  53. package/dist/web/index.d.ts +8 -0
  54. package/dist/web/index.js +9 -0
  55. package/dist/web/index.js.map +1 -0
  56. package/dist/web/offer.d.ts +87 -0
  57. package/dist/web/offer.js +169 -0
  58. package/dist/web/offer.js.map +1 -0
  59. package/package.json +64 -0
  60. package/src/BondingCurveAdmin.ts +590 -0
  61. package/src/FungibleToken.ts +11 -0
  62. package/src/FungibleTokenAdvancedAdmin.ts +260 -0
  63. package/src/FungibleTokenContract.ts +337 -0
  64. package/src/FungibleTokenStandardAdmin.ts +95 -0
  65. package/src/bid.ts +170 -0
  66. package/src/claim.ts +151 -0
  67. package/src/index.ts +8 -0
  68. package/src/offer.ts +164 -0
@@ -0,0 +1,260 @@
1
+ import {
2
+ AccountUpdate,
3
+ Bool,
4
+ DeployArgs,
5
+ method,
6
+ Permissions,
7
+ Provable,
8
+ PublicKey,
9
+ State,
10
+ state,
11
+ TokenContract,
12
+ UInt64,
13
+ VerificationKey,
14
+ AccountUpdateForest,
15
+ Struct,
16
+ Field,
17
+ TokenId,
18
+ } from "o1js";
19
+ import { Whitelist } from "@silvana-one/storage";
20
+ import { FungibleTokenAdminBase } from "./FungibleTokenContract.js";
21
+
22
+ export class AdvancedAdminData extends Struct({
23
+ totalSupply: UInt64,
24
+ requireAdminSignatureForMint: Bool,
25
+ anyoneCanMint: Bool,
26
+ }) {
27
+ static new(
28
+ params: {
29
+ totalSupply?: number; // default is UInt64.MAXINT()
30
+ requireAdminSignatureForMint?: boolean; // default is false
31
+ anyoneCanMint?: boolean; // default is false
32
+ } = {}
33
+ ): AdvancedAdminData {
34
+ const { totalSupply, requireAdminSignatureForMint, anyoneCanMint } = params;
35
+ return new AdvancedAdminData({
36
+ totalSupply: UInt64.from(totalSupply ?? 0),
37
+ requireAdminSignatureForMint: Bool(requireAdminSignatureForMint ?? false),
38
+ anyoneCanMint: Bool(anyoneCanMint ?? false),
39
+ });
40
+ }
41
+
42
+ pack(): Field {
43
+ const totalSupplyBits = this.totalSupply.value.toBits(64);
44
+ return Field.fromBits([
45
+ ...totalSupplyBits,
46
+ this.requireAdminSignatureForMint,
47
+ this.anyoneCanMint,
48
+ ]);
49
+ }
50
+
51
+ static unpack(packed: Field) {
52
+ const bits = packed.toBits(64 + 1 + 1);
53
+ const totalSupply = UInt64.Unsafe.fromField(
54
+ Field.fromBits(bits.slice(0, 64))
55
+ );
56
+ const requireAdminSignatureForMint = bits[64];
57
+ const anyoneCanMint = bits[64 + 1];
58
+ return new AdvancedAdminData({
59
+ totalSupply,
60
+ requireAdminSignatureForMint,
61
+ anyoneCanMint,
62
+ });
63
+ }
64
+ }
65
+
66
+ export interface FungibleTokenWhitelistedAdminDeployProps
67
+ extends Exclude<DeployArgs, undefined> {
68
+ adminPublicKey: PublicKey;
69
+ tokenContract: PublicKey;
70
+ totalSupply: UInt64;
71
+ whitelist: Whitelist;
72
+ requireAdminSignatureForMint: Bool;
73
+ anyoneCanMint: Bool;
74
+ }
75
+
76
+ /** A contract that grants permissions for administrative actions on a token.
77
+ *
78
+ * We separate this out into a dedicated contract. That way, when issuing a token, a user can
79
+ * specify their own rules for administrative actions, without changing the token contract itself.
80
+ *
81
+ * The advantage is that third party applications that only use the token in a non-privileged way
82
+ * can integrate against the unchanged token contract.
83
+ */
84
+ export class FungibleTokenAdvancedAdmin
85
+ extends TokenContract
86
+ implements FungibleTokenAdminBase
87
+ {
88
+ @state(PublicKey) adminPublicKey = State<PublicKey>();
89
+ @state(PublicKey) tokenContract = State<PublicKey>();
90
+ @state(Whitelist) whitelist = State<Whitelist>();
91
+ @state(Field) adminData = State<Field>();
92
+
93
+ /**
94
+ * Overrides the approveBase method to prevent transfers of tokens.
95
+ *
96
+ * @param forest - The account update forest.
97
+ */
98
+ async approveBase(forest: AccountUpdateForest) {
99
+ throw Error("Transfer not allowed");
100
+ }
101
+
102
+ async deploy(props: FungibleTokenWhitelistedAdminDeployProps) {
103
+ await super.deploy(props);
104
+ this.adminPublicKey.set(props.adminPublicKey);
105
+ this.tokenContract.set(props.tokenContract);
106
+ this.adminData.set(
107
+ new AdvancedAdminData({
108
+ totalSupply: props.totalSupply,
109
+ requireAdminSignatureForMint: props.requireAdminSignatureForMint,
110
+ anyoneCanMint: props.anyoneCanMint,
111
+ }).pack()
112
+ );
113
+ this.whitelist.set(props.whitelist);
114
+ this.account.permissions.set({
115
+ ...Permissions.default(),
116
+ setVerificationKey:
117
+ Permissions.VerificationKey.proofDuringCurrentVersion(),
118
+ setPermissions: Permissions.impossible(),
119
+ });
120
+ }
121
+
122
+ events = { updateWhitelist: Whitelist };
123
+
124
+ /** Update the verification key.
125
+ * Note that because we have set the permissions for setting
126
+ * the verification key to `impossibleDuringCurrentVersion()`,
127
+ * this will only be possible in case of a protocol update that requires an update.
128
+ */
129
+ @method
130
+ async updateVerificationKey(vk: VerificationKey) {
131
+ await this.ensureAdminSignature();
132
+ this.account.verificationKey.set(vk);
133
+ }
134
+
135
+ private async ensureAdminSignature() {
136
+ // We do not fetch the admin public key here to allow error handling during
137
+ // the fetching of the admin public key that should be done before calling this method
138
+ const admin = this.adminPublicKey.getAndRequireEquals();
139
+ const adminUpdate = AccountUpdate.createSigned(admin);
140
+ adminUpdate.body.useFullCommitment = Bool(true);
141
+ return adminUpdate;
142
+ }
143
+
144
+ @method.returns(Bool)
145
+ public async canMint(_accountUpdate: AccountUpdate) {
146
+ // We use many conditional account updates here to allow other contracts to call this method
147
+ // without hitting the account update limit
148
+ const address = _accountUpdate.body.publicKey;
149
+ const balanceChange = _accountUpdate.body.balanceChange;
150
+ balanceChange.isPositive().assertTrue();
151
+ const amount: UInt64 = balanceChange.magnitude;
152
+
153
+ const adminData = AdvancedAdminData.unpack(
154
+ this.adminData.getAndRequireEquals()
155
+ );
156
+ amount.assertLessThanOrEqual(adminData.totalSupply);
157
+
158
+ const tokenContract = this.tokenContract.getAndRequireEquals();
159
+ const tokenId = TokenId.derive(tokenContract); // it is NOT this.tokenId
160
+ const adminTokenId = this.deriveTokenId(); // it is NOT this.tokenId
161
+
162
+ // Does this guarantee that the call is from the token contract?
163
+ // TODO: If not, consider adding a sync method to handle the case when
164
+ // the contract will be called not from the token contract
165
+ // and the totalSupply will run out of sync with the token contract
166
+ _accountUpdate.body.tokenId.assertEquals(tokenId);
167
+
168
+ // Create a conditional AccountUpdate to check total supply in case it is limited
169
+ const maxAdditionalSupply = adminData.totalSupply.sub(amount);
170
+ const tokenUpdate = AccountUpdate.createIf(
171
+ adminData.totalSupply.equals(UInt64.MAXINT()).not(),
172
+ this.address,
173
+ adminTokenId
174
+ );
175
+ tokenUpdate.account.balance.requireBetween(
176
+ UInt64.zero,
177
+ maxAdditionalSupply
178
+ );
179
+ tokenUpdate.balance.addInPlace(amount);
180
+ this.self.approve(tokenUpdate);
181
+
182
+ const whitelist = this.whitelist.getAndRequireEquals();
183
+ const whitelistedAmount = await whitelist.getWhitelistedAmount(address);
184
+ whitelistedAmount.isSome
185
+ .or(adminData.anyoneCanMint)
186
+ .assertTrue("Cannot mint to non-whitelisted address");
187
+ const maxMintAmount = Provable.if(
188
+ adminData.anyoneCanMint,
189
+ Provable.if(
190
+ whitelistedAmount.isSome,
191
+ whitelistedAmount.value,
192
+ UInt64.MAXINT()
193
+ ), // blacklist
194
+ whitelistedAmount.value
195
+ );
196
+ amount.assertLessThanOrEqual(maxMintAmount);
197
+
198
+ // create a conditional account update to check if the tokens already have been minted
199
+ // we will keep track of the total amount minted in the admin contract
200
+ // It is the responsibility of Mina.transaction to fund the new account
201
+ // We will not handle it here to save one account update
202
+ const trackMintUpdate = AccountUpdate.createIf(
203
+ whitelist.isSome(), // we do not track minting if the whitelist is empty
204
+ address,
205
+ adminTokenId
206
+ );
207
+ trackMintUpdate.account.balance.requireBetween(
208
+ UInt64.zero,
209
+ maxMintAmount.sub(amount)
210
+ );
211
+ trackMintUpdate.balance.addInPlace(amount);
212
+ this.self.approve(trackMintUpdate);
213
+
214
+ // This conditional account update will be created only if admin signature is required
215
+ const adminSignatureUpdate = AccountUpdate.createIf(
216
+ adminData.requireAdminSignatureForMint,
217
+ this.adminPublicKey.getAndRequireEquals()
218
+ );
219
+ adminSignatureUpdate.requireSignature();
220
+ adminSignatureUpdate.body.useFullCommitment = Bool(true);
221
+
222
+ // We return true as we already checked that the mint is allowed
223
+ return Bool(true);
224
+ }
225
+
226
+ @method.returns(Bool)
227
+ public async canChangeAdmin(_admin: PublicKey) {
228
+ await this.ensureAdminSignature();
229
+ return Bool(true);
230
+ }
231
+
232
+ @method.returns(Bool)
233
+ public async canPause(): Promise<Bool> {
234
+ await this.ensureAdminSignature();
235
+ return Bool(true);
236
+ }
237
+
238
+ @method.returns(Bool)
239
+ public async canResume(): Promise<Bool> {
240
+ await this.ensureAdminSignature();
241
+ return Bool(true);
242
+ }
243
+
244
+ @method async updateWhitelist(whitelist: Whitelist) {
245
+ const admin = this.adminPublicKey.getAndRequireEquals();
246
+ const sender = this.sender.getUnconstrained();
247
+ const senderUpdate = AccountUpdate.createSigned(sender);
248
+ senderUpdate.body.useFullCommitment = Bool(true);
249
+ admin.assertEquals(sender);
250
+
251
+ this.whitelist.set(whitelist);
252
+ this.emitEvent("updateWhitelist", whitelist);
253
+ }
254
+
255
+ @method.returns(Bool)
256
+ public async canChangeVerificationKey(_vk: VerificationKey): Promise<Bool> {
257
+ await this.ensureAdminSignature();
258
+ return Bool(true);
259
+ }
260
+ }
@@ -0,0 +1,337 @@
1
+ import {
2
+ AccountUpdate,
3
+ AccountUpdateForest,
4
+ assert,
5
+ Bool,
6
+ DeployArgs,
7
+ Field,
8
+ Int64,
9
+ method,
10
+ Permissions,
11
+ Provable,
12
+ PublicKey,
13
+ SmartContract,
14
+ State,
15
+ state,
16
+ Struct,
17
+ TokenContract,
18
+ Types,
19
+ UInt64,
20
+ UInt8,
21
+ VerificationKey,
22
+ } from "o1js";
23
+
24
+ export type FungibleTokenAdminBase = SmartContract & {
25
+ canMint(accountUpdate: AccountUpdate): Promise<Bool>;
26
+ canChangeAdmin(admin: PublicKey): Promise<Bool>;
27
+ canPause(): Promise<Bool>;
28
+ canResume(): Promise<Bool>;
29
+ canChangeVerificationKey(vk: VerificationKey): Promise<Bool>;
30
+ };
31
+
32
+ export type FungibleTokenAdminConstructor = new (
33
+ adminPublicKey: PublicKey
34
+ ) => FungibleTokenAdminBase;
35
+
36
+ export interface FungibleTokenDeployProps
37
+ extends Exclude<DeployArgs, undefined> {
38
+ /** The token symbol. */
39
+ symbol: string;
40
+ /** A source code reference, which is placed within the `zkappUri` of the contract account.
41
+ * Typically a link to a file on github. */
42
+ src: string;
43
+ /** Setting this to `true` will allow changing the verification key later with a signature from the deployer. This will allow updating the token contract at a later stage, for instance to react to an update of the o1js library.
44
+ * Setting it to `false` will make changes to the contract impossible, unless there is a backward incompatible change to the protocol. (see https://docs.minaprotocol.com/zkapps/writing-a-zkapp/feature-overview/permissions#example-impossible-to-upgrade and https://minafoundation.github.io/mina-fungible-token/deploy.html) */
45
+ allowUpdates: boolean;
46
+ }
47
+
48
+ export const FungibleTokenErrors = {
49
+ noAdminKey: "could not fetch admin contract key",
50
+ noPermissionToChangeAdmin: "Not allowed to change admin contract",
51
+ tokenPaused: "Token is currently paused",
52
+ noPermissionToMint: "Not allowed to mint tokens",
53
+ noPermissionToPause: "Not allowed to pause token",
54
+ noPermissionToResume: "Not allowed to resume token",
55
+ noTransferFromCirculation: "Can't transfer to/from the circulation account",
56
+ noPermissionChangeAllowed:
57
+ "Can't change permissions for access or receive on token accounts",
58
+ flashMinting:
59
+ "Flash-minting or unbalanced transaction detected. Please make sure that your transaction is balanced, and that your `AccountUpdate`s are ordered properly, so that tokens are not received before they are sent.",
60
+ unbalancedTransaction: "Transaction is unbalanced",
61
+ };
62
+ export function FungibleTokenContract(
63
+ adminContract: FungibleTokenAdminConstructor
64
+ ) {
65
+ class FungibleToken extends TokenContract {
66
+ @state(UInt8)
67
+ decimals = State<UInt8>();
68
+ @state(PublicKey)
69
+ admin = State<PublicKey>();
70
+ @state(Bool)
71
+ paused = State<Bool>();
72
+
73
+ readonly events = {
74
+ SetAdmin: SetAdminEvent,
75
+ Pause: PauseEvent,
76
+ Mint: MintEvent,
77
+ Burn: BurnEvent,
78
+ BalanceChange: BalanceChangeEvent,
79
+ };
80
+
81
+ async deploy(props: FungibleTokenDeployProps) {
82
+ await super.deploy(props);
83
+ this.paused.set(Bool(true));
84
+ this.account.zkappUri.set(props.src);
85
+ this.account.tokenSymbol.set(props.symbol);
86
+
87
+ this.account.permissions.set({
88
+ ...Permissions.default(),
89
+ setVerificationKey: props.allowUpdates
90
+ ? Permissions.VerificationKey.proofDuringCurrentVersion()
91
+ : Permissions.VerificationKey.impossibleDuringCurrentVersion(),
92
+ setPermissions: Permissions.impossible(),
93
+ access: Permissions.proof(),
94
+ });
95
+ }
96
+
97
+ /** Update the verification key.
98
+ * This will only work when `allowUpdates` has been set to `true` during deployment.
99
+ */
100
+ @method
101
+ async updateVerificationKey(vk: VerificationKey) {
102
+ const adminContract = await this.getAdminContract();
103
+ const canChangeVerificationKey =
104
+ await adminContract.canChangeVerificationKey(vk);
105
+ canChangeVerificationKey.assertTrue(
106
+ FungibleTokenErrors.noPermissionToChangeAdmin
107
+ );
108
+ this.account.verificationKey.set(vk);
109
+ }
110
+
111
+ /** Initializes the account for tracking total circulation.
112
+ * @argument {PublicKey} admin - public key where the admin contract is deployed
113
+ * @argument {UInt8} decimals - number of decimals for the token
114
+ * @argument {Bool} startPaused - if set to `Bool(true), the contract will start in a mode where token minting and transfers are paused. This should be used for non-atomic deployments
115
+ */
116
+ @method
117
+ async initialize(admin: PublicKey, decimals: UInt8, startPaused: Bool) {
118
+ this.account.provedState.requireEquals(Bool(false));
119
+
120
+ this.admin.set(admin);
121
+ this.decimals.set(decimals);
122
+ this.paused.set(Bool(false));
123
+
124
+ this.paused.set(startPaused);
125
+
126
+ const accountUpdate = AccountUpdate.createSigned(
127
+ this.address,
128
+ this.deriveTokenId()
129
+ );
130
+ let permissions = Permissions.default();
131
+ // This is necessary in order to allow token holders to burn.
132
+ permissions.send = Permissions.none();
133
+ permissions.setPermissions = Permissions.impossible();
134
+ accountUpdate.account.permissions.set(permissions);
135
+ }
136
+
137
+ public async getAdminContract(): Promise<FungibleTokenAdminBase> {
138
+ const admin = await Provable.witnessAsync(PublicKey, async () => {
139
+ let pk = await this.admin.fetch();
140
+ assert(pk !== undefined, FungibleTokenErrors.noAdminKey);
141
+ return pk;
142
+ });
143
+ this.admin.requireEquals(admin);
144
+ return new adminContract(admin);
145
+ }
146
+
147
+ @method
148
+ async setAdmin(admin: PublicKey) {
149
+ const adminContract = await this.getAdminContract();
150
+ const canChangeAdmin = await adminContract.canChangeAdmin(admin);
151
+ canChangeAdmin.assertTrue(FungibleTokenErrors.noPermissionToChangeAdmin);
152
+ this.admin.set(admin);
153
+ this.emitEvent("SetAdmin", new SetAdminEvent({ adminKey: admin }));
154
+ }
155
+
156
+ @method.returns(AccountUpdate)
157
+ async mint(recipient: PublicKey, amount: UInt64): Promise<AccountUpdate> {
158
+ this.paused
159
+ .getAndRequireEquals()
160
+ .assertFalse(FungibleTokenErrors.tokenPaused);
161
+ const accountUpdate = this.internal.mint({ address: recipient, amount });
162
+ const adminContract = await this.getAdminContract();
163
+ const canMint = await adminContract.canMint(accountUpdate);
164
+ canMint.assertTrue(FungibleTokenErrors.noPermissionToMint);
165
+ recipient
166
+ .equals(this.address)
167
+ .assertFalse(FungibleTokenErrors.noTransferFromCirculation);
168
+ this.approve(accountUpdate);
169
+ this.emitEvent("Mint", new MintEvent({ recipient, amount }));
170
+ const circulationUpdate = AccountUpdate.create(
171
+ this.address,
172
+ this.deriveTokenId()
173
+ );
174
+ circulationUpdate.balanceChange = Int64.fromUnsigned(amount);
175
+ return accountUpdate;
176
+ }
177
+
178
+ @method.returns(AccountUpdate)
179
+ async burn(from: PublicKey, amount: UInt64): Promise<AccountUpdate> {
180
+ this.paused
181
+ .getAndRequireEquals()
182
+ .assertFalse(FungibleTokenErrors.tokenPaused);
183
+ const accountUpdate = this.internal.burn({ address: from, amount });
184
+ const circulationUpdate = AccountUpdate.create(
185
+ this.address,
186
+ this.deriveTokenId()
187
+ );
188
+ from
189
+ .equals(this.address)
190
+ .assertFalse(FungibleTokenErrors.noTransferFromCirculation);
191
+ circulationUpdate.balanceChange = Int64.fromUnsigned(amount).neg();
192
+ this.emitEvent("Burn", new BurnEvent({ from, amount }));
193
+ return accountUpdate;
194
+ }
195
+
196
+ @method
197
+ async pause() {
198
+ const adminContract = await this.getAdminContract();
199
+ const canPause = await adminContract.canPause();
200
+ canPause.assertTrue(FungibleTokenErrors.noPermissionToPause);
201
+ this.paused.set(Bool(true));
202
+ this.emitEvent("Pause", new PauseEvent({ isPaused: Bool(true) }));
203
+ }
204
+
205
+ @method
206
+ async resume() {
207
+ const adminContract = await this.getAdminContract();
208
+ const canResume = await adminContract.canResume();
209
+ canResume.assertTrue(FungibleTokenErrors.noPermissionToResume);
210
+ this.paused.set(Bool(false));
211
+ this.emitEvent("Pause", new PauseEvent({ isPaused: Bool(false) }));
212
+ }
213
+
214
+ @method
215
+ async transfer(from: PublicKey, to: PublicKey, amount: UInt64) {
216
+ this.paused
217
+ .getAndRequireEquals()
218
+ .assertFalse(FungibleTokenErrors.tokenPaused);
219
+ from
220
+ .equals(this.address)
221
+ .assertFalse(FungibleTokenErrors.noTransferFromCirculation);
222
+ to.equals(this.address).assertFalse(
223
+ FungibleTokenErrors.noTransferFromCirculation
224
+ );
225
+ this.internal.send({ from, to, amount });
226
+ }
227
+
228
+ checkPermissionsUpdate(update: AccountUpdate) {
229
+ let permissions = update.update.permissions;
230
+
231
+ let { access, receive } = permissions.value;
232
+ let accessIsNone = Provable.equal(
233
+ Types.AuthRequired,
234
+ access,
235
+ Permissions.none()
236
+ );
237
+ let receiveIsNone = Provable.equal(
238
+ Types.AuthRequired,
239
+ receive,
240
+ Permissions.none()
241
+ );
242
+ let updateAllowed = accessIsNone.and(receiveIsNone);
243
+
244
+ assert(
245
+ updateAllowed.or(permissions.isSome.not()),
246
+ FungibleTokenErrors.noPermissionChangeAllowed
247
+ );
248
+ }
249
+
250
+ /** Approve `AccountUpdate`s that have been created outside of the token contract.
251
+ *
252
+ * @argument {AccountUpdateForest} updates - The `AccountUpdate`s to approve. Note that the forest size is limited by the base token contract, @see TokenContract.MAX_ACCOUNT_UPDATES The current limit is 9.
253
+ */
254
+ @method
255
+ async approveBase(updates: AccountUpdateForest): Promise<void> {
256
+ this.paused
257
+ .getAndRequireEquals()
258
+ .assertFalse(FungibleTokenErrors.tokenPaused);
259
+ let totalBalance = Int64.from(0);
260
+ this.forEachUpdate(updates, (update, usesToken) => {
261
+ // Make sure that the account permissions are not changed
262
+ this.checkPermissionsUpdate(update);
263
+ this.emitEventIf(
264
+ usesToken,
265
+ "BalanceChange",
266
+ new BalanceChangeEvent({
267
+ address: update.publicKey,
268
+ amount: update.balanceChange,
269
+ })
270
+ );
271
+ // Don't allow transfers to/from the account that's tracking circulation
272
+ update.publicKey
273
+ .equals(this.address)
274
+ .and(usesToken)
275
+ .assertFalse(FungibleTokenErrors.noTransferFromCirculation);
276
+ totalBalance = Provable.if(
277
+ usesToken,
278
+ totalBalance.add(update.balanceChange),
279
+ totalBalance
280
+ );
281
+ totalBalance.isPositive().assertFalse(FungibleTokenErrors.flashMinting);
282
+ });
283
+ totalBalance.assertEquals(
284
+ Int64.zero,
285
+ FungibleTokenErrors.unbalancedTransaction
286
+ );
287
+ }
288
+
289
+ @method.returns(UInt64)
290
+ async getBalanceOf(address: PublicKey): Promise<UInt64> {
291
+ const account = AccountUpdate.create(
292
+ address,
293
+ this.deriveTokenId()
294
+ ).account;
295
+ const balance = account.balance.get();
296
+ account.balance.requireEquals(balance);
297
+ return balance;
298
+ }
299
+
300
+ /** Reports the current circulating supply
301
+ * This does take into account currently unreduced actions.
302
+ */
303
+ async getCirculating(): Promise<UInt64> {
304
+ let circulating = await this.getBalanceOf(this.address);
305
+ return circulating;
306
+ }
307
+
308
+ @method.returns(UInt8)
309
+ async getDecimals(): Promise<UInt8> {
310
+ return this.decimals.getAndRequireEquals();
311
+ }
312
+ }
313
+ return FungibleToken;
314
+ }
315
+
316
+ export class SetAdminEvent extends Struct({
317
+ adminKey: PublicKey,
318
+ }) {}
319
+
320
+ export class PauseEvent extends Struct({
321
+ isPaused: Bool,
322
+ }) {}
323
+
324
+ export class MintEvent extends Struct({
325
+ recipient: PublicKey,
326
+ amount: UInt64,
327
+ }) {}
328
+
329
+ export class BurnEvent extends Struct({
330
+ from: PublicKey,
331
+ amount: UInt64,
332
+ }) {}
333
+
334
+ export class BalanceChangeEvent extends Struct({
335
+ address: PublicKey,
336
+ amount: Int64,
337
+ }) {}
@@ -0,0 +1,95 @@
1
+ import {
2
+ AccountUpdate,
3
+ assert,
4
+ Bool,
5
+ DeployArgs,
6
+ method,
7
+ Permissions,
8
+ Provable,
9
+ PublicKey,
10
+ SmartContract,
11
+ State,
12
+ state,
13
+ VerificationKey,
14
+ } from "o1js";
15
+ import { FungibleTokenAdminBase } from "./FungibleTokenContract.js";
16
+
17
+ export interface FungibleTokenAdminDeployProps
18
+ extends Exclude<DeployArgs, undefined> {
19
+ adminPublicKey: PublicKey;
20
+ }
21
+
22
+ /** A contract that grants permissions for administrative actions on a token.
23
+ *
24
+ * We separate this out into a dedicated contract. That way, when issuing a token, a user can
25
+ * specify their own rules for administrative actions, without changing the token contract itself.
26
+ *
27
+ * The advantage is that third party applications that only use the token in a non-privileged way
28
+ * can integrate against the unchanged token contract.
29
+ */
30
+ export class FungibleTokenAdmin
31
+ extends SmartContract
32
+ implements FungibleTokenAdminBase
33
+ {
34
+ @state(PublicKey)
35
+ private adminPublicKey = State<PublicKey>();
36
+
37
+ async deploy(props: FungibleTokenAdminDeployProps) {
38
+ await super.deploy(props);
39
+ this.adminPublicKey.set(props.adminPublicKey);
40
+ this.account.permissions.set({
41
+ ...Permissions.default(),
42
+ setVerificationKey:
43
+ Permissions.VerificationKey.impossibleDuringCurrentVersion(),
44
+ setPermissions: Permissions.impossible(),
45
+ });
46
+ }
47
+
48
+ /** Update the verification key.
49
+ * Note that because we have set the permissions for setting the verification key to `impossibleDuringCurrentVersion()`, this will only be possible in case of a protocol update that requires an update.
50
+ */
51
+ @method
52
+ async updateVerificationKey(vk: VerificationKey) {
53
+ this.account.verificationKey.set(vk);
54
+ }
55
+
56
+ private async ensureAdminSignature() {
57
+ const admin = await Provable.witnessAsync(PublicKey, async () => {
58
+ let pk = await this.adminPublicKey.fetch();
59
+ assert(pk !== undefined, "could not fetch admin public key");
60
+ return pk;
61
+ });
62
+ this.adminPublicKey.requireEquals(admin);
63
+ return AccountUpdate.createSigned(admin);
64
+ }
65
+
66
+ @method.returns(Bool)
67
+ public async canMint(_accountUpdate: AccountUpdate) {
68
+ await this.ensureAdminSignature();
69
+ return Bool(true);
70
+ }
71
+
72
+ @method.returns(Bool)
73
+ public async canChangeAdmin(_admin: PublicKey) {
74
+ await this.ensureAdminSignature();
75
+ return Bool(true);
76
+ }
77
+
78
+ @method.returns(Bool)
79
+ public async canPause(): Promise<Bool> {
80
+ await this.ensureAdminSignature();
81
+ return Bool(true);
82
+ }
83
+
84
+ @method.returns(Bool)
85
+ public async canResume(): Promise<Bool> {
86
+ await this.ensureAdminSignature();
87
+ return Bool(true);
88
+ }
89
+
90
+ @method.returns(Bool)
91
+ public async canChangeVerificationKey(_vk: VerificationKey): Promise<Bool> {
92
+ await this.ensureAdminSignature();
93
+ return Bool(true);
94
+ }
95
+ }