├── canister_ids.json ├── README.md ├── .gitmodules ├── .gitignore ├── dfx.json ├── demo.sh ├── LICENSE └── src └── registry.mo /canister_ids.json: -------------------------------------------------------------------------------- 1 | { 2 | "registry": { 3 | "ic": "6u36y-tyaaa-aaaah-qaawq-cai" 4 | } 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## One-click token issuance app 2 | 3 | Canisters: 4 | 5 | * token canister 6 | * token registry canister 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/ic-token"] 2 | path = src/ic-token 3 | url = https://github.com/dfinance-tech/ic-token 4 | branch = main 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Various IDEs and Editors 2 | .vscode/ 3 | .idea/ 4 | **/*~ 5 | 6 | # Mac OSX temporary files 7 | .DS_Store 8 | **/.DS_Store 9 | 10 | # dfx temporary files 11 | .dfx/ 12 | 13 | # frontend code 14 | node_modules/ 15 | dist/ 16 | -------------------------------------------------------------------------------- /dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "registry": { 4 | "main": "src/registry.mo" 5 | } 6 | }, 7 | "defaults": { 8 | "build": { 9 | "packtool": "" 10 | } 11 | }, 12 | "networks": { 13 | "local": { 14 | "bind": "127.0.0.1:8000", 15 | "type": "ephemeral" 16 | } 17 | }, 18 | "version": 1 19 | } 20 | -------------------------------------------------------------------------------- /demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # clear 6 | dfx stop 7 | rm -rf .dfx 8 | 9 | ALICE_HOME=$(mktemp -d -t alice-temp) 10 | HOME=$ALICE_HOME 11 | 12 | ALICE_PUBLIC_KEY="principal \"$( \ 13 | HOME=$ALICE_HOME dfx identity get-principal 14 | )\"" 15 | 16 | dfx start --background 17 | dfx canister create registry 18 | dfx build registry 19 | 20 | echo ==Create registry canister 21 | dfx canister install registry 22 | TOKEN0_ID=$(dfx canister id registry) 23 | 24 | echo ==Create token0 canister 25 | eval dfx canister call registry createToken "'(\"Token0\", \"T0\", 3, 1000000)'" 26 | 27 | echo ==Create token1 canister 28 | eval dfx canister call registry createToken "'(\"Token1\", \"T1\", 3, 1000000)'" 29 | 30 | echo ==Create token2 canister 31 | eval dfx canister call registry createToken "'(\"Token2\", \"T2\", 3, 1000000)'" 32 | 33 | echo ==Get Token list 34 | eval dfx canister call registry getTokenList 35 | 36 | echo ==Get User Token List 37 | eval dfx canister call registry getUserTokenList "'($ALICE_PUBLIC_KEY)'" 38 | 39 | echo ==Get Token Info by id 40 | eval dfx canister call registry getTokenInfoById "'(0)'" 41 | eval dfx canister call registry getTokenInfoById "'(1)'" 42 | eval dfx canister call registry getTokenInfoById "'(2)'" 43 | eval dfx canister call registry getTokenInfoById "'(3)'" 44 | 45 | dfx stop -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the 13 | copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other 16 | entities that control, are controlled by, or are under common control with 17 | that entity. For the purposes of this definition, "control" means (i) the 18 | power, direct or indirect, to cause the direction or management of such 19 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent 20 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of 21 | such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity exercising 24 | permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation source, and 28 | configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical transformation 31 | or translation of a Source form, including but not limited to compiled 32 | object code, generated documentation, and conversions to other media types. 33 | 34 | "Work" shall mean the work of authorship, whether in Source or Object form, 35 | made available under the License, as indicated by a copyright notice that is 36 | included in or attached to the work (an example is provided in the Appendix 37 | below). 38 | 39 | "Derivative Works" shall mean any work, whether in Source or Object form, 40 | that is based on (or derived from) the Work and for which the editorial 41 | revisions, annotations, elaborations, or other modifications represent, as a 42 | whole, an original work of authorship. For the purposes of this License, 43 | Derivative Works shall not include works that remain separable from, or 44 | merely link (or bind by name) to the interfaces of, the Work and Derivative 45 | Works thereof. 46 | 47 | "Contribution" shall mean any work of authorship, including the original 48 | version of the Work and any modifications or additions to that Work or 49 | Derivative Works thereof, that is intentionally submitted to Licensor for 50 | inclusion in the Work by the copyright owner or by an individual or Legal 51 | Entity authorized to submit on behalf of the copyright owner. For the 52 | purposes of this definition, "submitted" means any form of electronic, 53 | verbal, or written communication sent to the Licensor or its 54 | representatives, including but not limited to communication on electronic 55 | mailing lists, source code control systems, and issue tracking systems that 56 | are managed by, or on behalf of, the Licensor for the purpose of discussing 57 | and improving the Work, but excluding communication that is conspicuously 58 | marked or otherwise designated in writing by the copyright owner as "Not a 59 | Contribution." 60 | 61 | "Contributor" shall mean Licensor and any individual or Legal Entity on 62 | behalf of whom a Contribution has been received by Licensor and subsequently 63 | incorporated within the Work. 64 | 65 | 2. Grant of Copyright License. Subject to the terms and conditions of this 66 | License, each Contributor hereby grants to You a perpetual, worldwide, 67 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 68 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 69 | sublicense, and distribute the Work and such Derivative Works in Source or 70 | Object form. 71 | 72 | 3. Grant of Patent License. Subject to the terms and conditions of this 73 | License, each Contributor hereby grants to You a perpetual, worldwide, 74 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in 75 | this section) patent license to make, have made, use, offer to sell, sell, 76 | import, and otherwise transfer the Work, where such license applies only to 77 | those patent claims licensable by such Contributor that are necessarily 78 | infringed by their Contribution(s) alone or by combination of their 79 | Contribution(s) with the Work to which such Contribution(s) was submitted. 80 | If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this 84 | License for that Work shall terminate as of the date such litigation is 85 | filed. 86 | 87 | 4. Redistribution. You may reproduce and distribute copies of the Work or 88 | Derivative Works thereof in any medium, with or without modifications, and 89 | in Source or Object form, provided that You meet the following conditions: 90 | 91 | a. You must give any other recipients of the Work or Derivative Works a 92 | copy of this License; and 93 | 94 | b. You must cause any modified files to carry prominent notices stating 95 | that You changed the files; and 96 | 97 | c. You must retain, in the Source form of any Derivative Works that You 98 | distribute, all copyright, patent, trademark, and attribution notices 99 | from the Source form of the Work, excluding those notices that do not 100 | pertain to any part of the Derivative Works; and 101 | 102 | d. If the Work includes a "NOTICE" text file as part of its distribution, 103 | then any Derivative Works that You distribute must include a readable 104 | copy of the attribution notices contained within such NOTICE file, 105 | excluding those notices that do not pertain to any part of the Derivative 106 | Works, in at least one of the following places: within a NOTICE text file 107 | distributed as part of the Derivative Works; within the Source form or 108 | documentation, if provided along with the Derivative Works; or, within a 109 | display generated by the Derivative Works, if and wherever such 110 | third-party notices normally appear. The contents of the NOTICE file are 111 | for informational purposes only and do not modify the License. You may 112 | add Your own attribution notices within Derivative Works that You 113 | distribute, alongside or as an addendum to the NOTICE text from the Work, 114 | provided that such additional attribution notices cannot be construed as 115 | modifying the License. 116 | 117 | You may add Your own copyright statement to Your modifications and may 118 | provide additional or different license terms and conditions for use, 119 | reproduction, or distribution of Your modifications, or for any such 120 | Derivative Works as a whole, provided Your use, reproduction, and 121 | distribution of the Work otherwise complies with the conditions stated in 122 | this License. 123 | 124 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 125 | Contribution intentionally submitted for inclusion in the Work by You to the 126 | Licensor shall be under the terms and conditions of this License, without 127 | any additional terms or conditions. Notwithstanding the above, nothing 128 | herein shall supersede or modify the terms of any separate license agreement 129 | you may have executed with Licensor regarding such Contributions. 130 | 131 | 6. Trademarks. This License does not grant permission to use the trade names, 132 | trademarks, service marks, or product names of the Licensor, except as 133 | required for reasonable and customary use in describing the origin of the 134 | Work and reproducing the content of the NOTICE file. 135 | 136 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 137 | writing, Licensor provides the Work (and each Contributor provides its 138 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 139 | KIND, either express or implied, including, without limitation, any 140 | warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 141 | FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining 142 | the appropriateness of using or redistributing the Work and assume any risks 143 | associated with Your exercise of permissions under this License. 144 | 145 | 8. Limitation of Liability. In no event and under no legal theory, whether in 146 | tort (including negligence), contract, or otherwise, unless required by 147 | applicable law (such as deliberate and grossly negligent acts) or agreed to 148 | in writing, shall any Contributor be liable to You for damages, including 149 | any direct, indirect, special, incidental, or consequential damages of any 150 | character arising as a result of this License or out of the use or inability 151 | to use the Work (including but not limited to damages for loss of goodwill, 152 | work stoppage, computer failure or malfunction, or any and all other 153 | commercial damages or losses), even if such Contributor has been advised of 154 | the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 157 | Derivative Works thereof, You may choose to offer, and charge a fee for, 158 | acceptance of support, warranty, indemnity, or other liability obligations 159 | and/or rights consistent with this License. However, in accepting such 160 | obligations, You may act only on Your own behalf and on Your sole 161 | responsibility, not on behalf of any other Contributor, and only if You 162 | agree to indemnify, defend, and hold each Contributor harmless for any 163 | liability incurred by, or claims asserted against, such Contributor by 164 | reason of your accepting any such warranty or additional liability. 165 | 166 | END OF TERMS AND CONDITIONS 167 | 168 | LLVM EXCEPTION TO THE APACHE 2.0 LICENSE 169 | 170 | As an exception, if, as a result of your compiling your source code, portions 171 | of this Software are embedded into an Object form of such source code, you may 172 | redistribute such embedded portions in such Object form without complying with 173 | the conditions of Sections 4(a), 4(b) and 4(d) of the License. 174 | 175 | In addition, if you combine or link compiled forms of this Software with 176 | software that is licensed under the GPLv2 ("Combined Software") and if a court 177 | of competent jurisdiction determines that the patent provision (Section 3), the 178 | indemnity provision (Section 9) or other Section of the License conflicts with 179 | the conditions of the GPLv2, you may retroactively and prospectively choose to 180 | deem waived or otherwise exclude such Section(s) of the License, but only in 181 | their entirety and only with respect to the Combined Software. 182 | 183 | END OF LLVM EXCEPTION 184 | 185 | APPENDIX: How to apply the Apache License to your work. 186 | 187 | To apply the Apache License to your work, attach the following boilerplate 188 | notice, with the fields enclosed by brackets "[]" replaced with your own 189 | identifying information. (Don't include the brackets!) The text should be 190 | enclosed in the appropriate comment syntax for the file format. We also 191 | recommend that a file or class name and description of purpose be included on 192 | the same "printed page" as the copyright notice for easier identification 193 | within third-party archives. 194 | 195 | Copyright [yyyy] [name of copyright owner] 196 | 197 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 198 | this file except in compliance with the License. You may obtain a copy of the 199 | License at 200 | 201 | http://www.apache.org/licenses/LICENSE-2.0 202 | 203 | Unless required by applicable law or agreed to in writing, software distributed 204 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 205 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 206 | specific language governing permissions and limitations under the License. 207 | 208 | END OF APPENDIX 209 | -------------------------------------------------------------------------------- /src/registry.mo: -------------------------------------------------------------------------------- 1 | /** 2 | * Module : registry.mo 3 | * Copyright : 2021 DFinance Team 4 | * License : Apache 2.0 with LLVM Exception 5 | * Maintainer : DFinance Team 6 | * Stability : Experimental 7 | */ 8 | 9 | import HashMap "mo:base/HashMap"; 10 | import Array "mo:base/Array"; 11 | import Nat "mo:base/Nat"; 12 | import Order "mo:base/Order"; 13 | import Hash "mo:base/Hash"; 14 | import Error "mo:base/Error"; 15 | import Principal "mo:base/Principal"; 16 | import Iter "mo:base/Iter"; 17 | import Time "mo:base/Time"; 18 | import Text "mo:base/Text"; 19 | import Result "mo:base/Result"; 20 | import Cycles = "mo:base/ExperimentalCycles"; 21 | import Token "./ic-token/motoko/src/token"; 22 | 23 | shared(msg) actor class TokenRegistry(owner: Principal, _feeTokenId: Principal, _fee: Nat) = this { 24 | type TxReceipt = Result.Result; 28 | type Metadata = { 29 | logo : Text; 30 | name : Text; 31 | symbol : Text; 32 | decimals : Nat8; 33 | totalSupply : Nat; 34 | owner : Principal; 35 | fee : Nat; 36 | }; 37 | public type Operation = { 38 | #mint; 39 | #burn; 40 | #transfer; 41 | #transferFrom; 42 | #approve; 43 | }; 44 | public type TxRecord = { 45 | caller: ?Principal; 46 | op: Operation; 47 | index: Nat; 48 | from: Principal; 49 | to: Principal; 50 | amount: Nat; 51 | fee: Nat; 52 | timestamp: Time.Time; 53 | }; 54 | public type TokenInfo = { 55 | index: Nat; 56 | logo: Text; 57 | name: Text; 58 | symbol: Text; 59 | decimals: Nat8; 60 | totalSupply: Nat; 61 | owner: Principal; 62 | fee: Nat; 63 | canisterId: Principal; 64 | timestamp: Int; 65 | }; 66 | public type TokenActor = actor { 67 | allowance: shared (owner: Principal, spender: Principal) -> async Nat; 68 | approve: shared (spender: Principal, value: Nat) -> async TxReceipt; 69 | balanceOf: (owner: Principal) -> async Nat; 70 | decimals: () -> async Nat; 71 | name: () -> async Text; 72 | symbol: () -> async Text; 73 | totalSupply: () -> async Nat; 74 | getMetadata: () -> async Metadata; 75 | getTransaction: (index: Nat) -> async TxRecord; 76 | transfer: shared (to: Principal, value: Nat) -> async TxReceipt; 77 | transferFrom: shared (from: Principal, to: Principal, value: Nat) -> async TxReceipt; 78 | }; 79 | private stable var _owner: Principal = owner; 80 | private stable var numTokens: Nat = 0; 81 | private stable var cyclesPerToken: Nat = 2000000000000; // 2 trillion cycles for each token canister 82 | private stable var maxNumTokens: Nat = 100; 83 | private stable var maxNumTokensPerId: Nat = 2; 84 | private stable var feeTokenId: Principal = _feeTokenId; 85 | private stable var fee: Nat = _fee; 86 | 87 | private stable var tokenEntries : [(Principal, TokenInfo)] = []; 88 | private stable var userTokenNumEntries : [(Principal, Nat)] = []; 89 | private var tokens = HashMap.HashMap(0, Principal.equal, Principal.hash); 90 | private var userTokenNum = HashMap.HashMap(0, Principal.equal, Principal.hash); 91 | 92 | public type Stats = { 93 | owner: Principal; 94 | numTokens: Nat; 95 | cyclesPerToken: Nat; 96 | maxNumTokens: Nat; 97 | maxNumTokensPerId: Nat; 98 | cycles: Nat; 99 | feeTokenId: Principal; 100 | fee: Nat; 101 | }; 102 | 103 | type CanisterSettings = { 104 | controllers : ?[Principal]; 105 | compute_allocation : ?Nat; 106 | memory_allocation : ?Nat; 107 | freezing_threshold : ?Nat; 108 | }; 109 | type CanisterId = { 110 | canister_id: Principal; 111 | }; 112 | type InstallMode = { 113 | #install; 114 | #reinstall; 115 | #upgrade; 116 | }; 117 | type InstallCodeParams = { 118 | mode: InstallMode; 119 | canister_id: Principal; 120 | wasm_module: Blob; 121 | arg: Blob; 122 | }; 123 | type UpdateSettingsParams = { 124 | canister_id: Principal; 125 | settings: CanisterSettings; 126 | }; 127 | type Status = { 128 | #running; 129 | #stopping; 130 | #stopped; 131 | }; 132 | type CanisterStatus = { 133 | status: Status; 134 | settings: CanisterSettings; 135 | module_hash: ?Blob; 136 | memory_size: Nat; 137 | cycles: Nat; 138 | }; 139 | public type ICActor = actor { 140 | create_canister: shared(settings: ?CanisterSettings) -> async CanisterId; 141 | update_settings: shared(params: UpdateSettingsParams) -> async (); 142 | install_code: shared(params: InstallCodeParams) -> async (); 143 | canister_status: query(canister_id: CanisterId) -> async CanisterStatus; 144 | }; 145 | let IC: ICActor = actor("aaaaa-aa"); 146 | 147 | system func preupgrade() { 148 | tokenEntries := Iter.toArray(tokens.entries()); 149 | userTokenNumEntries := Iter.toArray(userTokenNum.entries()); 150 | }; 151 | 152 | system func postupgrade() { 153 | tokens := HashMap.fromIter(tokenEntries.vals(), 1, Principal.equal, Principal.hash); 154 | tokenEntries := []; 155 | userTokenNum := HashMap.fromIter(userTokenNumEntries.vals(), 1, Principal.equal, Principal.hash); 156 | userTokenNumEntries := []; 157 | }; 158 | 159 | public shared(msg) func createToken( 160 | logo: Text, 161 | name: Text, 162 | symbol: Text, 163 | decimals: Nat8, 164 | totalSupply: Nat, 165 | fee: Nat 166 | ): async Principal { 167 | if(numTokens >= maxNumTokens) { 168 | throw Error.reject("Exceeds max number of tokens"); 169 | }; 170 | var userTokenCount: Nat = 0; 171 | switch(userTokenNum.get(msg.caller)) { 172 | case (?tokenCount) { 173 | if(tokenCount > maxNumTokensPerId) { 174 | throw Error.reject("exceeds max number of tokens per user"); 175 | }; 176 | userTokenCount := tokenCount; 177 | }; 178 | case (_) {}; 179 | }; 180 | // charge fee 181 | let feeToken: TokenActor = actor(Principal.toText(feeTokenId)); 182 | switch(await feeToken.transferFrom(msg.caller, Principal.fromActor(this), fee)) { 183 | case(#ok(txid)) {}; 184 | case(#err(e)) { 185 | throw Error.reject("fee transfer failed"); 186 | }; 187 | }; 188 | // create token canister 189 | Cycles.add(cyclesPerToken); 190 | let token = await Token.Token(logo, name, symbol, decimals, totalSupply, msg.caller, fee); 191 | let cid = Principal.fromActor(token); 192 | let info: TokenInfo = { 193 | index = numTokens; 194 | logo = logo; 195 | name = name; 196 | symbol = symbol; 197 | decimals = decimals; 198 | totalSupply = totalSupply; 199 | owner = msg.caller; 200 | fee = fee; 201 | canisterId = cid; 202 | timestamp = Time.now(); 203 | }; 204 | tokens.put(cid, info); 205 | numTokens += 1; 206 | userTokenNum.put(msg.caller, userTokenCount + 1); 207 | return cid; 208 | }; 209 | 210 | public shared(msg) func setController(canisterId: Principal): async Bool { 211 | switch(tokens.get(canisterId)) { 212 | case(?info) { 213 | assert(msg.caller == info.owner or msg.caller == _owner); 214 | let controllers: ?[Principal] = ?[msg.caller, Principal.fromActor(this)]; 215 | let settings: CanisterSettings = { 216 | controllers = controllers; 217 | compute_allocation = null; 218 | memory_allocation = null; 219 | freezing_threshold = null; 220 | }; 221 | let params: UpdateSettingsParams = { 222 | canister_id = canisterId; 223 | settings = settings; 224 | }; 225 | await IC.update_settings(params); 226 | return true; 227 | }; 228 | case(_) { return false }; 229 | } 230 | }; 231 | 232 | public shared(msg) func getTokenCanisterStatus(canister_id: Principal): async ?CanisterStatus { 233 | switch(tokens.get(canister_id)) { 234 | case(?info) { 235 | let param: CanisterId = { 236 | canister_id = canister_id; 237 | }; 238 | let status = await IC.canister_status(param); 239 | return ?status; 240 | }; 241 | case(_) {return null}; 242 | } 243 | }; 244 | 245 | public shared(msg) func claimFee(): async TxReceipt { 246 | assert(msg.caller == _owner); 247 | let feeToken: TokenActor = actor(Principal.toText(feeTokenId)); 248 | let balance: Nat = await feeToken.balanceOf(Principal.fromActor(this)); 249 | return await feeToken.transfer(msg.caller, balance); 250 | }; 251 | 252 | public shared(msg) func setFee(newFee: Nat): async Bool { 253 | assert(msg.caller == _owner); 254 | fee := newFee; 255 | return true; 256 | }; 257 | 258 | public shared(msg) func modifyTokenInfo(info: TokenInfo): async Bool { 259 | assert(msg.caller == _owner); 260 | tokens.put(info.canisterId, info); 261 | return true; 262 | }; 263 | 264 | public shared(msg) func addToken(tid: Principal): async Bool { 265 | let token: TokenActor = actor(Principal.toText(tid)); 266 | let metadata: Metadata = await token.getMetadata(); 267 | let tx: TxRecord = await token.getTransaction(0); 268 | let info: TokenInfo = { 269 | index = numTokens; 270 | logo = metadata.logo; 271 | name = metadata.name; 272 | symbol = metadata.symbol; 273 | decimals = metadata.decimals; 274 | totalSupply = metadata.totalSupply; 275 | owner = metadata.owner; 276 | fee = metadata.fee; 277 | canisterId = tid; 278 | timestamp = tx.timestamp; 279 | }; 280 | tokens.put(tid, info); 281 | numTokens += 1; 282 | return true; 283 | }; 284 | 285 | public shared(msg) func setNumTokens(n: Nat): async Bool { 286 | assert(msg.caller == _owner); 287 | numTokens := n; 288 | return true; 289 | }; 290 | 291 | public shared(msg) func setMaxTokenNumber(n: Nat) { 292 | assert(msg.caller == _owner); 293 | maxNumTokens := n; 294 | }; 295 | 296 | public shared(msg) func setMaxTokenNumberPerUser(n: Nat) { 297 | assert(msg.caller == _owner); 298 | maxNumTokensPerId := n; 299 | }; 300 | 301 | public shared(msg) func setCyclesPerToken(n: Nat) { 302 | assert(msg.caller == _owner); 303 | cyclesPerToken := n; 304 | }; 305 | 306 | public shared(msg) func setOwner(newOwner: Principal) { 307 | assert(msg.caller == _owner); 308 | _owner := newOwner; 309 | }; 310 | 311 | public shared(msg) func removeToken(id: Principal) { 312 | assert(msg.caller == _owner); 313 | tokens.delete(id); 314 | }; 315 | 316 | public query func getCyclesBalance(): async Nat { 317 | return Cycles.balance(); 318 | }; 319 | 320 | public query func getTokenCount(): async Nat { 321 | return numTokens; 322 | }; 323 | 324 | public query func getMaxTokenNumber(): async Nat { 325 | return maxNumTokens; 326 | }; 327 | 328 | public query func getMaxTokenNumberPerUser(): async Nat { 329 | return maxNumTokensPerId; 330 | }; 331 | 332 | public query func getStats(): async Stats { 333 | return { 334 | owner = _owner; 335 | numTokens = numTokens; 336 | cyclesPerToken = cyclesPerToken; 337 | maxNumTokens = maxNumTokens; 338 | maxNumTokensPerId = maxNumTokensPerId; 339 | cycles = Cycles.balance(); 340 | feeTokenId = feeTokenId; 341 | fee = fee; 342 | }; 343 | }; 344 | 345 | public query func getUserTokenNumber(id: Principal): async Nat { 346 | switch(userTokenNum.get(id)) { 347 | case (?num) { return num; }; 348 | case (_) { return 0; }; 349 | } 350 | }; 351 | 352 | public query func getTokenList(): async [TokenInfo] { 353 | var tokenList: [TokenInfo] = []; 354 | for((id, token) in tokens.entries()) { 355 | tokenList := Array.append(tokenList, [token]); 356 | }; 357 | tokenList 358 | }; 359 | 360 | // for paging 361 | public query func getTokens(start: Nat, num: Nat): async ([TokenInfo], Nat) { 362 | var tokenList: [TokenInfo] = []; 363 | func order (a: (Principal, TokenInfo), b: (Principal, TokenInfo)): Order.Order { 364 | return Nat.compare(a.1.index, b.1.index); 365 | }; 366 | let temp = Iter.toArray(tokens.entries()); 367 | let sorted = Array.sort(temp, order); 368 | let limit: Nat = if(start + num > sorted.size()) { 369 | sorted.size() - start 370 | } else { 371 | num 372 | }; 373 | for (i in Iter.range(0, limit-1)) { 374 | tokenList := Array.append(tokenList, [sorted[i+start].1]); 375 | }; 376 | (tokenList, sorted.size()) 377 | }; 378 | 379 | public query func getTokensByName(t: Text, start: Nat, num: Nat) : async ([TokenInfo], Nat) { 380 | var temp: [TokenInfo] = []; 381 | let p : Text.Pattern = #text t; 382 | for ((id, token) in tokens.entries()) { 383 | if (Text.contains(token.name, p) or Text.contains(token.symbol, p)) { 384 | temp := Array.append(temp, [token]); 385 | }; 386 | }; 387 | var tokenList: [TokenInfo] = []; 388 | let limit: Nat = if(start + num > temp.size()) { 389 | temp.size() - start 390 | } else { 391 | num 392 | }; 393 | for (i in Iter.range(0, limit-1)) { 394 | tokenList := Array.append(tokenList, [temp[start+i]]) 395 | }; 396 | (tokenList, temp.size()) 397 | }; 398 | 399 | public query func getUserTokenList(user: Principal): async [TokenInfo] { 400 | var tokenList: [TokenInfo] = []; 401 | for((index, token) in tokens.entries()) { 402 | if(token.owner == user) { 403 | tokenList := Array.append(tokenList, [token]); 404 | }; 405 | }; 406 | tokenList 407 | }; 408 | 409 | public query func getTokenInfo(cid: Principal): async ?TokenInfo { 410 | tokens.get(cid) 411 | }; 412 | }; 413 | --------------------------------------------------------------------------------