{"id":19256770,"url":"https://github.com/oxheadalpha/composed-fa2-example","last_synced_at":"2025-04-21T15:31:03.431Z","repository":{"id":42961741,"uuid":"468468227","full_name":"oxheadalpha/composed-fa2-example","owner":"oxheadalpha","description":"Example of modular FA2 contract generator (fa2-contracts).","archived":true,"fork":false,"pushed_at":"2023-10-06T15:51:39.000Z","size":643,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-12T19:45:13.112Z","etag":null,"topics":["fa2","ligo","smart-contracts","taquito","tezos"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oxheadalpha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-03-10T18:38:46.000Z","updated_at":"2024-04-10T19:02:53.000Z","dependencies_parsed_at":"2022-09-01T03:50:45.100Z","dependency_job_id":null,"html_url":"https://github.com/oxheadalpha/composed-fa2-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxheadalpha%2Fcomposed-fa2-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxheadalpha%2Fcomposed-fa2-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxheadalpha%2Fcomposed-fa2-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oxheadalpha%2Fcomposed-fa2-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oxheadalpha","download_url":"https://codeload.github.com/oxheadalpha/composed-fa2-example/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250080558,"owners_count":21371526,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fa2","ligo","smart-contracts","taquito","tezos"],"created_at":"2024-11-09T19:07:03.532Z","updated_at":"2025-04-21T15:31:03.122Z","avatar_url":"https://github.com/oxheadalpha.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Content\n\nThis is an example project to demonstrate usage of the\n[@oxheadalpha/fa2-contracts](https://github.com/oxheadalpha/nft-tutorial/blob/master/packages/fa2-contracts/README.md)\npackage, a library and a tool to generate modular Tezos FA2 contracts and\nTypeScript interfaces.\n\n## Table of Contents\n\n* [Initial Setup](#initial-setup)\n* [Custom FA2 Contract](#custom-fa2-contract)\n  * [Generate Base Fungible Token FA2 Contract](#generate-base-fungible-token-fa2-contract)\n  * [Extend Base LIGO Contract](#extend-base-ligo-contract)\n  * [Customize Contract Origination](#customize-contract-origination)\n  * [Define TypeScript API to Access Custom Entry Points](#define-typescript-api-to-access-custom-entry-points)\n\n## Initial Setup\n\nOnce we have created a TypeScript project, we need to add a development\ndependency on `@oxheadalpha/fa2-contracts` package and dependency on\n`@oxheadalpha/fa2-interfaces` package.\n\n```sh\n$ yarn add -D @oxheadalpha/fa2-contracts\n...\n$ yarn add @oxheadalpha/fa2-interfaces\n```\n\n## Custom FA2 Contract\n\nAs an example, we are going to implement a single fungible token FA2 contract\nwhere the FA2 token can be exchanged for Tez. Any address can invoke the `mint`\ncontract entry point and transfer some Tez to exchange for the FA2 token. Any\nFA2 token owner can also exchange FA2 tokens for Tez by calling the `burn`\ncontract entry point. The exchange rate is always one Mutez per one FA2 token.\nIn addition, the contract collects a flat exchange fee that can be set by the\ncontract admin. The contract admin can change the exchange fee and withdraw\ncollected fees. The contract admin can pause and unpause the contract. If the\ncontract is paused, token owners cannot transfer, mint and burn tokens.\n\nFirst, we will generate the LIGO code and TypeScript API for the base fungible\ntoken contract using `tzGen` tool from the `@oxheadalpha/fa2-contracts` package.\nThen, we will extend the contract code with the custom `mint`, `burn` and the\nadmin entry points. Finally, we will extend generated TypeScript API with the new\nentry points' methods and storage origination function.\n\n### Generate Base Fungible Token FA2 Contract\n\nImport LIGO library sources from `@oxheadalpha/fa2-contracts` package with the\n`yarn tzGen import-ligo` command. We are using the default destination directory\nfor the LIGO code `./ligo`.\n\n```sh\n$ yarn tzgen import-ligo\nimporting LIGO sources from ~/composed-fa2-example/node_modules/@oxheadalpha/fa2-contracts/ligo to ~/composed-fa2-example/ligo\nLIGO sources imported to ~/composed-fa2-example/ligo\n```\n\nInitialize `tzGen` tool environment. We are using the default settings for LIGO\nsource code directory (`./ligo`) and TypeScript source code directory (`./src`).\nFor the contract compilation output we specify `.\\dist` directory.\n\n```sh\n$ yarn tzgen init --compile-out ./dist\n~/composed-fa2-example/tzgen.json config file created\n```\n\nGenerate base FA2 contract specification: single fungible token (FT) and simple\npausable admin. We do not specify any standard minting functionality since we\nare going to extend the base contract with the custom implementation.\n\n```sh\n$ yarn tzgen spec my_contract.json --kind FT --admin PAUSABLE\n~/composed-fa2-example/my_contract.json spec file created\n```\n\nGenerate base contract LIGO source code\n\n```sh\n$ yarn tzgen contract my_contract.json base_ft_contract.mligo\n~/composed-fa2-example/ligo/src/base_ft_contract.mligo is generated\n```\n\nGenerate TypeScript API for the base contract.\n\n```sh\n$ yarn tzgen type-script my_contract.json base_ft_contract.ts\n~/composed-fa2-example/src/base_ft_contract.ts is generated\n```\n\nThe generated contract already has the standard FA2 functionality: token\ntransfer, operators etc. On the next step we are going to extend it with the\ncustom mint and burn implementation, and exchange admin entry points to\nchange the fee and withdraw collected fees.\n\n### Extend Base LIGO Contract\n\nFirst we need to define LIGO types for the custom contract storage and the main\nentry point. In the [./ligo/src](./ligo/src) directory we create a new contract\nfile [tzfa2_contract.mligo](./ligo/src/tzfa2_contract.mligo) and include the\ngenerated [base_ft_contract.mligo](./ligo/src/base_ft_contract.mligo). Then we\ndefine a new storage type as a record combining `asset_storage` from the\ngenerated contract and the exchange fee related fields:\n\n```ocaml\n#include \"base_ft_contract.mligo\"\n\ntype tzfa2_storage = {\n  asset : asset_storage;\n  fee : tez;\n  collected_fees: tez;\n}\n```\n\nDefine the custom entry points type:\n\n```ocaml\ntype change_fee_param = {\n  old_fee : tez;\n  new_fee : tez;\n}\n\ntype tzfa2_entrypoints =\n  | Mint of tez (* param is expected exchange fee *)\n  | Burn of nat (* param is the number of tokens to burn *)\n  | Change_fee of change_fee_param\n  | Withdraw_fees (* the admin withdraws collected fees *)\n```\n\nDefine a new main entry point function and dispatch the calls to handler functions:\n\n```ocaml\nlet custom_entrypoints (param, storage : tzfa2_entrypoints * tzfa2_storage)\n    : (operation list) * tzfa2_storage =\n  (* will add custom entry points handlers later *)\n  ([] : operation list), storage\n\nmodule TzFa2 = struct\n\n  [@entry]\n  let asset (param : Asset.entrypoints) (storage : tzfa2_storage)\n      : (operation list) * tzfa2_storage =\n    (* dispatch call to the generated contract main function implementation *)\n    let ops, new_asset = Asset.main (param, storage.asset) in\n    let new_s = { storage with asset = new_asset } in\n    (ops, new_s)\n\n  [@entry]\n  let tzfa2 (param : tzfa2_entrypoints) (storage : tzfa2_storage)\n      : (operation list) * tzfa2_storage =\n    custom_entrypoints (param, storage)\n\nend\n```\n\nTo test that our contract code compiles, we can add a build script based on\n`tzGen` to the `package.json` file:\n\n```json\n\"build:contract\": \"yarn tzgen michelson tzfa2_contract tzfa2_contract --main TzFa2\"\n```\n\nNow we can run the following command\n\n```sh\n$ yarn build:contract\n```\n\nany time we made changes to the contract code.\n\nThe next step is to implement handlers for the custom entry points. The final\nimplementation of custom entry points can be found in\n[tzfa2_contract.mligo](./ligo/src/tzfa2_contract.mligo). The implementation uses\nthe `fail_if_not_admin` guard for the custom admin entry points, and\n`fail_if_paused` and `fail_if_not_minter` guards for the `mint` and `burn` entry\npoints, Those guards are part of the public admin and minter admin modules API\n(see\n[LIGO modules](https://github.com/oxheadalpha/nft-tutorial/blob/master/packages/fa2-contracts/README.md#cameligo-modules)\ndescription in `@oxheadalpha/fa2-contracts` package for more details). If we\ndecide to change the specification for the contract admin and/or minter admin and\nregenerate the base FA2 contract, we do not need to change our custom extension\ncode, since the public modules API will remain the same.\n\n### Customize Contract Origination\n\nOn previous steps we have generated the TypeScript\n[createStorage()](./src/base_ft_contract.ts) function. The function accepts a\nparameter object with three fields:\n\n* `metadata` - a string representing the contract TZIP-16 metadata\n* `owner` - the address of the contract owner/admin\n* `token` - token metadata\n\nThe custom contract extension defines two extra fields: `fee` and `collected-fees`\nthat need to be initialized.\n\n```ocaml\ntype tzfa2_storage = {\n  asset : asset_storage;\n  fee : tez;\n  collected_fees: tez;\n}\n```\n\nThe `collected_fees` should be 0 mutez during the contract origination and we\nwould need an extra parameter specifying the initial fee. Based on the generated\n`createStorage()` function, we can define the\n[createCustomStorage()](./src/origination.ts) function like this:\n\n```typescript\nexport const createCustomStorage = (\n  metadata: string,\n  owner: address,\n  token: TokenMetadataInternal,\n  fee: mutez //fee is a part of the custom storage extension\n) =\u003e {\n  const baseStorage = createStorage({\n    metadata,\n    owner,\n    token\n  });\n  return { asset: baseStorage, fee, collected_fees: 0 };\n};\n```\n\n### Define TypeScript API to Access Custom Entry Points\n\nWe have defined the following custom entry points:\n\n* `change_fee` - the admin can change the exchange fee.\n* `withdraw_fees` - the admin can withdraw collected fees.\n* `mint` - any token owner can mint some FA2 tokens in exchange for transferred\n  tez.\n* `burn` - any token owner can exchange his FA2 tokens for tez.\n\nWe are going to split those entry points into two different TypeScript interfaces:\n`ExchangeAdminContract` and `MinterContract`. Each entry point is represented by\nthe interface method that returns `ContractMethod\u003cContractProvider\u003e`. The\nunderlying implementation will invoke Taquito's contract method and can be mixed\nwith other contract method calls.\n\n```typescript\nexport interface ExchangeAdminContract {\n  withdrawFees: () =\u003e ContractMethod\u003cContractProvider\u003e;\n  changeFee: (\n    old_fee: mutez,\n    new_fee: mutez\n  ) =\u003e ContractMethod\u003cContractProvider\u003e;\n}\n\nexport interface MinterContract {\n  mint: (expected_fee: mutez) =\u003e ContractMethod\u003cContractProvider\u003e;\n  burn: (ntokens: nat) =\u003e ContractMethod\u003cContractProvider\u003e;\n}\n```\n\nIn the next step we are going to implement two constructor functions:\n`ExchangeAdmin` and `Minter`. The actual implementation can be found\n[here](./src/contract_interface.ts).\n[See](https://github.com/oxheadalpha/nft-tutorial/tree/master/packages/fa2-interfaces#custom-contracts)\nfor more details.\n\nNow we can use newly-defined API like this:\n\n```typescript\nconst ownerApi = (\n  await tezosApi(toolkit).at(contractAddress)\n).with(Minter);\n\nawait runMethod(ownerApi.mint(1000000), { amount: 5000000, mutez: true });\n```\n\nand\n\n```typescript\nconst adminApi = (await tezosApi(toolkit).at(contractAddress)).with(\n  ExchangeAdmin\n);\nconst run = runMethod(adminApi.changeFee(1, 2));\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxheadalpha%2Fcomposed-fa2-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foxheadalpha%2Fcomposed-fa2-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foxheadalpha%2Fcomposed-fa2-example/lists"}