diff --git a/FungibleToken.test.ts b/FungibleToken.test.ts index 04a1176..9370d75 100644 --- a/FungibleToken.test.ts +++ b/FungibleToken.test.ts @@ -69,12 +69,14 @@ describe("token integration", async () => { adminPublicKey: tokenAdmin, }) await tokenAContract.deploy({ - admin: tokenAdmin, symbol: "tokA", - src: "", - decimals: UInt8.from(9), + src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts", }) - await tokenAContract.initialize() + await tokenAContract.initialize( + tokenAdmin, + UInt8.from(9), + Bool(true), + ) }) tx.sign([ @@ -97,13 +99,14 @@ describe("token integration", async () => { adminPublicKey: tokenBAdmin, }) await tokenBContract.deploy({ - admin: tokenBAdmin, symbol: "tokB", - src: "", - decimals: UInt8.from(9), - startUnpaused: true, + src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts", }) - await tokenBContract.initialize() + await tokenBContract.initialize( + tokenBAdmin, + UInt8.from(9), + Bool(false), + ) }) tx.sign([deployer.key, tokenB.key, tokenBAdmin.key]) diff --git a/FungibleToken.ts b/FungibleToken.ts index ce3006f..d9938f1 100644 --- a/FungibleToken.ts +++ b/FungibleToken.ts @@ -13,7 +13,6 @@ import { State, state, Struct, - TokenContract, TokenContractV2, Types, UInt64, @@ -22,19 +21,11 @@ import { import { FungibleTokenAdmin, FungibleTokenAdminBase } from "./FungibleTokenAdmin.js" interface FungibleTokenDeployProps extends Exclude { - /** Address of the contract controlling permissions for administrative actions */ - admin: PublicKey /** The token symbol. */ symbol: string /** A source code reference, which is placed within the `zkappUri` of the contract account. * Typically a link to a file on github. */ src: string - /** Number of decimals in a unit */ - decimals: UInt8 - /** Unless this is set to `true`, the tokens will start in paused mode, - * and will need to be explicitly resumed by calling the `resume()` method. - * You should only set this to `true` in atomic deploys. */ - startUnpaused?: boolean } export class FungibleToken extends TokenContractV2 { @@ -60,24 +51,29 @@ export class FungibleToken extends TokenContractV2 { async deploy(props: FungibleTokenDeployProps) { await super.deploy(props) - - this.admin.set(props.admin) - this.decimals.set(props.decimals) - this.paused.set(Bool(false)) - - this.account.tokenSymbol.set(props.symbol) + this.paused.set(Bool(true)) this.account.zkappUri.set(props.src) - - if (props.startUnpaused) { - this.paused.set(Bool(false)) - } else { - this.paused.set(Bool(true)) - } } - // ** Initializes the account for tracking total circulation. */ + /** 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 + */ @method - async initialize() { + async initialize( + admin: PublicKey, + decimals: UInt8, + startPaused: Bool, + ) { + this.account.provedState.requireEquals(Bool(false)) + super.init() + 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. diff --git a/documentation/api.md b/documentation/api.md index a23d0e0..86375ab 100644 --- a/documentation/api.md +++ b/documentation/api.md @@ -34,21 +34,26 @@ The on-chain state is defined as follows: @state(Bool) paused = State() ``` -The `deploy` function takes as arguments +The `deploy()` function takes as arguments -- The public key of the account that the admin contract has been deployed to -- A symbol to use as the token symbol +- A string to use as the token symbol - A string pointing to the source code of the contract -- when following the standard, this should point to the source of the standard implementation on github + +Immediately after deploying the contract -- ideally, in the same transaction -- the contract needs +to be initialized via the `initialize()` method. Its arguments are + +- The public key of the account that the admin contract has been deployed to - A `UInt8` for the number of decimals -- An optional `boolean` to signify whether token transfers should be enabled immediately. Unless - this is supplied and set to `true`, the token contract will be in a paused state initially, and - the `resume()` will need to be called before tokens can be minted or transferred. This is safer if - you have a non-atomic deploy (i.e., if you do not have the admin contract deployed in the same - transaction as the token contract itself). - -and initializes the state of the contract. Initially, the circulating supply is set to zero, as no -tokens have been created yet. +- A `Bool` to determine whether the token contract should start in paused mode. whether token + transfers should be enabled immediately. If set to `Bool(true)`, the token contract will be in a + paused state initially, and the `resume()` method will need to be called before tokens can be + minted or transferred. This is safer if you have a non-atomic deploy (i.e., if you do not have the + admin contract deployed in the same transaction as the token contract is itself is deployed and + initialized). + +This method initializes the state of the contract. Initially, the circulating supply is set to zero, +as no tokens have been created yet. ## Methods diff --git a/documentation/deploy.md b/documentation/deploy.md index 724eeee..3b9db3a 100644 --- a/documentation/deploy.md +++ b/documentation/deploy.md @@ -5,6 +5,8 @@ token contract itself, and initializing the contract ## Deploying an admin contract +The first step is deploying the admin contract via its `deploy()` function. + The admin contract handles permissions for privileged actions, such as minting. It is called by the token contract whenever a user tries to do a privileged action. @@ -19,29 +21,35 @@ If you want to change your admin contract, you can write a contract that `extend [!NOTE] Note that if you want to use a custom admin contract, you should write the admin contract from scratch. Inheriting from `FungibleTokenAdmin` and overwriting specific methods might not work. -You can find an example of a custom admin contract in `FungibleToken.test.ts`. +You can find an example of a custom admin contract in `FungibleToken.test.ts`.ß -## Deploying the token contract +The `initialize()` method of `FungibleToken` takes as one argument the address of the admin +contract. If you have written your own admin contract, you will also need to set +`FungibleToken.AdminContract` to that class. -The `deploy` function of `FungibleToken` takes as one argument the address of the admin contract. If -you have written your own admin contract, you will also need to set `FungibleToken.AdminContract` to -that class. +[!NOTE] If you do not use the `FungibleToken` class as is, third parties that want to integrate your +token will need to use your custom contract as well. -[!NOTE] If you do not use the `FungibleToken` as is, third parties that want to integrate your token -will need to use your custom contract as well. +## Initializing and deploying the token contract -## Initializing the token contract +Next, the token contract needs to be deployed, via its `deploy()` function. After being deployed, the token contract needs to be initialized, by calling the `initialize()` -method. That method creates an account on the chain that will be used to track the current -circulation of the token. +method. That method initializes the contract state, and creates an account on the chain that will be +used to track the current circulation of the token. [!NOTE] All three steps above can be carried out in a single transaction, or in separate -transactions. [!NOTE] Each of the three steps requires funding a new account on the chain via -`AccountUpdate.fundNewAccount`. [!NOTE] Per default, the token contract will start in paused mode. -If you perform all the steps in one single transaction, you can instead opt for starting it in -non-paused mode. Otherwise, you will need to call `resume()` before any tokens can be minted or -transferred. +transactions. It is highly recommended to have a single transaction with all three steps. + +[!NOTE] Unless you have a very good reason, please use one transaction that deploys the admin +contract, deploys the token contract, and calls `initialize()` on the token contract. + +[!NOTE] Each of the three steps requires funding a new account on the chain via +`AccountUpdate.fundNewAccount`. + +[!NOTE] If you use separate transactions for deploying the admin contract and deploying and +initializing the token contract, you should start the token contract in paused mode, and only call +`resume()` after you have verified that the admin contract has been successfully deployed. Refer to [examples/e2e.eg.ts](https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts) diff --git a/documentation/introduction.md b/documentation/introduction.md index fd67369..15ac7b3 100644 --- a/documentation/introduction.md +++ b/documentation/introduction.md @@ -29,12 +29,14 @@ const deployTx = await Mina.transaction({ AccountUpdate.fundNewAccount(deployer, 3) await adminContract.deploy({ adminPublicKey: admin.publicKey }) await token.deploy({ - admin: admin.publicKey, symbol: "abc", src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts", - decimals: UInt8.from(9), }) - await tokenBContract.initialize() + await tokenBContract.initialize( + admin.publicKey, + UInt8.from(9), + Bool(false), + ) }) await deployTx.prove() deployTx.sign([deployer.key, contract.privateKey, admin.privateKey]) diff --git a/examples/concurrent-transfer.eg.ts b/examples/concurrent-transfer.eg.ts index 0bb8e09..bc13859 100644 --- a/examples/concurrent-transfer.eg.ts +++ b/examples/concurrent-transfer.eg.ts @@ -1,4 +1,5 @@ -import { AccountUpdate, Mina, PrivateKey, PublicKey, UInt64, UInt8 } from "o1js" +import { AccountUpdate, Bool, Mina, PrivateKey, PublicKey, UInt64, UInt8 } from "o1js" +import { setLightnetAccountManagerEndpoint } from "o1js/dist/node/lib/mina/fetch.js" import { FungibleToken, FungibleTokenAdmin } from "../index.js" const url = "https://proxy.devnet.minaexplorer.com/graphql" @@ -88,17 +89,18 @@ const deployTx = await Mina.transaction({ AccountUpdate.fundNewAccount(feepayer.publicKey, 3) await adminContract.deploy({ adminPublicKey: admin.publicKey }) await token.deploy({ - admin: admin.publicKey, symbol: "abc", - src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts", - decimals: UInt8.from(9), - // We can set `startUnpaused` to true here, because we are doing an atomic deployment + src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts", + }) + await token.initialize( + admin.publicKey, + UInt8.from(9), + // We can set `startPaused` to `Bool(false)` here, because we are doing an atomic deployment // If you are not deploying the admin and token contracts in the same transaction, // it is safer to start the tokens paused, and resume them only after verifying that // the admin contract has been deployed - startUnpaused: true, - }) - await token.initialize() + Bool(false), + ) }) await deployTx.prove() deployTx.sign([feepayer.privateKey, contract.privateKey, admin.privateKey]) diff --git a/examples/e2e.eg.ts b/examples/e2e.eg.ts index 209f521..38a8faf 100644 --- a/examples/e2e.eg.ts +++ b/examples/e2e.eg.ts @@ -1,5 +1,5 @@ import { equal } from "node:assert" -import { AccountUpdate, Mina, PrivateKey, UInt64, UInt8 } from "o1js" +import { AccountUpdate, Bool, Mina, PrivateKey, UInt64, UInt8 } from "o1js" import { FungibleToken, FungibleTokenAdmin } from "../index.js" const localChain = await Mina.LocalBlockchain({ @@ -25,17 +25,18 @@ const deployTx = await Mina.transaction({ AccountUpdate.fundNewAccount(deployer, 3) await adminContract.deploy({ adminPublicKey: admin.publicKey }) await token.deploy({ - admin: admin.publicKey, symbol: "abc", - src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts", - decimals: UInt8.from(9), - // We can set `startUnpaused` to true here, because we are doing an atomic deployment + src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts", + }) + await token.initialize( + admin.publicKey, + UInt8.from(9), + // We can set `startPaused` to `Bool(false)` here, because we are doing an atomic deployment // If you are not deploying the admin and token contracts in the same transaction, // it is safer to start the tokens paused, and resume them only after verifying that // the admin contract has been deployed - startUnpaused: true, - }) - await token.initialize() + Bool(false), + ) }) await deployTx.prove() deployTx.sign([deployer.key, contract.privateKey, admin.privateKey]) diff --git a/examples/escrow.eg.ts b/examples/escrow.eg.ts index 506fe33..da02165 100644 --- a/examples/escrow.eg.ts +++ b/examples/escrow.eg.ts @@ -1,6 +1,7 @@ import { equal } from "node:assert" import { AccountUpdate, + Bool, DeployArgs, method, Mina, @@ -80,17 +81,18 @@ const deployTokenTx = await Mina.transaction({ AccountUpdate.fundNewAccount(deployer, 3) await adminContract.deploy({ adminPublicKey: admin.publicKey }) await token.deploy({ - admin: admin.publicKey, symbol: "abc", - src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/escrow.eg.ts", - decimals: UInt8.from(9), - // We can set `startUnpaused` to true here, because we are doing an atomic deployment + src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts", + }) + await token.initialize( + admin.publicKey, + UInt8.from(9), + // We can set `startPaused` to `Bool(false)` here, because we are doing an atomic deployment // If you are not deploying the admin and token contracts in the same transaction, // it is safer to start the tokens paused, and resume them only after verifying that // the admin contract has been deployed - startUnpaused: true, - }) - await token.initialize() + Bool(false), + ) }) await deployTokenTx.prove() deployTokenTx.sign([deployer.key, tokenContract.privateKey, admin.privateKey])