UNPKG

@silvana-one/token

Version:

Silvana Fungible Token Library

295 lines 13.7 kB
import { __decorate, __metadata } from "tslib"; import { AccountUpdate, AccountUpdateForest, assert, Bool, Int64, method, Permissions, Provable, PublicKey, State, state, Struct, TokenContract, Types, UInt64, UInt8, VerificationKey, } from "o1js"; export const FungibleTokenErrors = { noAdminKey: "could not fetch admin contract key", noPermissionToChangeAdmin: "Not allowed to change admin contract", tokenPaused: "Token is currently paused", noPermissionToMint: "Not allowed to mint tokens", noPermissionToPause: "Not allowed to pause token", noPermissionToResume: "Not allowed to resume token", noTransferFromCirculation: "Can't transfer to/from the circulation account", noPermissionChangeAllowed: "Can't change permissions for access or receive on token accounts", flashMinting: "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.", unbalancedTransaction: "Transaction is unbalanced", }; export function FungibleTokenContract(adminContract) { class FungibleToken extends TokenContract { constructor() { super(...arguments); this.decimals = State(); this.admin = State(); this.paused = State(); this.events = { SetAdmin: SetAdminEvent, Pause: PauseEvent, Mint: MintEvent, Burn: BurnEvent, BalanceChange: BalanceChangeEvent, }; } async deploy(props) { await super.deploy(props); this.paused.set(Bool(true)); this.account.zkappUri.set(props.src); this.account.tokenSymbol.set(props.symbol); this.account.permissions.set({ ...Permissions.default(), setVerificationKey: props.allowUpdates ? Permissions.VerificationKey.proofDuringCurrentVersion() : Permissions.VerificationKey.impossibleDuringCurrentVersion(), setPermissions: Permissions.impossible(), access: Permissions.proof(), }); } /** Update the verification key. * This will only work when `allowUpdates` has been set to `true` during deployment. */ async updateVerificationKey(vk) { const adminContract = await this.getAdminContract(); const canChangeVerificationKey = await adminContract.canChangeVerificationKey(vk); canChangeVerificationKey.assertTrue(FungibleTokenErrors.noPermissionToChangeAdmin); this.account.verificationKey.set(vk); } /** Initializes the account for tracking total circulation. * @argument {PublicKey} admin - public key where the admin contract is deployed * @argument {UInt8} decimals - number of decimals for the token * @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 */ async initialize(admin, decimals, startPaused) { this.account.provedState.requireEquals(Bool(false)); this.admin.set(admin); this.decimals.set(decimals); this.paused.set(Bool(false)); this.paused.set(startPaused); const accountUpdate = AccountUpdate.createSigned(this.address, this.deriveTokenId()); let permissions = Permissions.default(); // This is necessary in order to allow token holders to burn. permissions.send = Permissions.none(); permissions.setPermissions = Permissions.impossible(); accountUpdate.account.permissions.set(permissions); } async getAdminContract() { const admin = await Provable.witnessAsync(PublicKey, async () => { let pk = await this.admin.fetch(); assert(pk !== undefined, FungibleTokenErrors.noAdminKey); return pk; }); this.admin.requireEquals(admin); return new adminContract(admin); } async setAdmin(admin) { const adminContract = await this.getAdminContract(); const canChangeAdmin = await adminContract.canChangeAdmin(admin); canChangeAdmin.assertTrue(FungibleTokenErrors.noPermissionToChangeAdmin); this.admin.set(admin); this.emitEvent("SetAdmin", new SetAdminEvent({ adminKey: admin })); } async mint(recipient, amount) { this.paused .getAndRequireEquals() .assertFalse(FungibleTokenErrors.tokenPaused); const accountUpdate = this.internal.mint({ address: recipient, amount }); const adminContract = await this.getAdminContract(); const canMint = await adminContract.canMint(accountUpdate); canMint.assertTrue(FungibleTokenErrors.noPermissionToMint); recipient .equals(this.address) .assertFalse(FungibleTokenErrors.noTransferFromCirculation); this.approve(accountUpdate); this.emitEvent("Mint", new MintEvent({ recipient, amount })); const circulationUpdate = AccountUpdate.create(this.address, this.deriveTokenId()); circulationUpdate.balanceChange = Int64.fromUnsigned(amount); return accountUpdate; } async burn(from, amount) { this.paused .getAndRequireEquals() .assertFalse(FungibleTokenErrors.tokenPaused); const accountUpdate = this.internal.burn({ address: from, amount }); const circulationUpdate = AccountUpdate.create(this.address, this.deriveTokenId()); from .equals(this.address) .assertFalse(FungibleTokenErrors.noTransferFromCirculation); circulationUpdate.balanceChange = Int64.fromUnsigned(amount).neg(); this.emitEvent("Burn", new BurnEvent({ from, amount })); return accountUpdate; } async pause() { const adminContract = await this.getAdminContract(); const canPause = await adminContract.canPause(); canPause.assertTrue(FungibleTokenErrors.noPermissionToPause); this.paused.set(Bool(true)); this.emitEvent("Pause", new PauseEvent({ isPaused: Bool(true) })); } async resume() { const adminContract = await this.getAdminContract(); const canResume = await adminContract.canResume(); canResume.assertTrue(FungibleTokenErrors.noPermissionToResume); this.paused.set(Bool(false)); this.emitEvent("Pause", new PauseEvent({ isPaused: Bool(false) })); } async transfer(from, to, amount) { this.paused .getAndRequireEquals() .assertFalse(FungibleTokenErrors.tokenPaused); from .equals(this.address) .assertFalse(FungibleTokenErrors.noTransferFromCirculation); to.equals(this.address).assertFalse(FungibleTokenErrors.noTransferFromCirculation); this.internal.send({ from, to, amount }); } checkPermissionsUpdate(update) { let permissions = update.update.permissions; let { access, receive } = permissions.value; let accessIsNone = Provable.equal(Types.AuthRequired, access, Permissions.none()); let receiveIsNone = Provable.equal(Types.AuthRequired, receive, Permissions.none()); let updateAllowed = accessIsNone.and(receiveIsNone); assert(updateAllowed.or(permissions.isSome.not()), FungibleTokenErrors.noPermissionChangeAllowed); } /** Approve `AccountUpdate`s that have been created outside of the token contract. * * @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. */ async approveBase(updates) { this.paused .getAndRequireEquals() .assertFalse(FungibleTokenErrors.tokenPaused); let totalBalance = Int64.from(0); this.forEachUpdate(updates, (update, usesToken) => { // Make sure that the account permissions are not changed this.checkPermissionsUpdate(update); this.emitEventIf(usesToken, "BalanceChange", new BalanceChangeEvent({ address: update.publicKey, amount: update.balanceChange, })); // Don't allow transfers to/from the account that's tracking circulation update.publicKey .equals(this.address) .and(usesToken) .assertFalse(FungibleTokenErrors.noTransferFromCirculation); totalBalance = Provable.if(usesToken, totalBalance.add(update.balanceChange), totalBalance); totalBalance.isPositive().assertFalse(FungibleTokenErrors.flashMinting); }); totalBalance.assertEquals(Int64.zero, FungibleTokenErrors.unbalancedTransaction); } async getBalanceOf(address) { const account = AccountUpdate.create(address, this.deriveTokenId()).account; const balance = account.balance.get(); account.balance.requireEquals(balance); return balance; } /** Reports the current circulating supply * This does take into account currently unreduced actions. */ async getCirculating() { let circulating = await this.getBalanceOf(this.address); return circulating; } async getDecimals() { return this.decimals.getAndRequireEquals(); } } __decorate([ state(UInt8), __metadata("design:type", Object) ], FungibleToken.prototype, "decimals", void 0); __decorate([ state(PublicKey), __metadata("design:type", Object) ], FungibleToken.prototype, "admin", void 0); __decorate([ state(Bool), __metadata("design:type", Object) ], FungibleToken.prototype, "paused", void 0); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [VerificationKey]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "updateVerificationKey", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, UInt8, Bool]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "initialize", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "setAdmin", null); __decorate([ method.returns(AccountUpdate), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, UInt64]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "mint", null); __decorate([ method.returns(AccountUpdate), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, UInt64]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "burn", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "pause", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "resume", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, PublicKey, UInt64]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "transfer", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [AccountUpdateForest]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "approveBase", null); __decorate([ method.returns(UInt64), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey]), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "getBalanceOf", null); __decorate([ method.returns(UInt8), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], FungibleToken.prototype, "getDecimals", null); return FungibleToken; } export class SetAdminEvent extends Struct({ adminKey: PublicKey, }) { } export class PauseEvent extends Struct({ isPaused: Bool, }) { } export class MintEvent extends Struct({ recipient: PublicKey, amount: UInt64, }) { } export class BurnEvent extends Struct({ from: PublicKey, amount: UInt64, }) { } export class BalanceChangeEvent extends Struct({ address: PublicKey, amount: Int64, }) { } //# sourceMappingURL=FungibleTokenContract.js.map