├── pyfift ├── __init__.py ├── base │ ├── __init__.py │ ├── app.py │ ├── keypair.py │ ├── fift.py │ └── lite_client.py ├── core │ ├── __init__.py │ ├── boc.py │ ├── code.py │ ├── state_init.py │ ├── contract_address.py │ ├── contract.py │ ├── internal_msg.py │ └── deploy_contract.py ├── wallet │ ├── __init__.py │ ├── wallet_base.py │ └── wallet_v3_r2.py └── nft │ ├── utils.py │ ├── content.py │ ├── nft_deploy.py │ └── nft_collection.py ├── token-contract ├── .gitignore ├── ft │ ├── build │ │ ├── wton.addr │ │ ├── jetton-wallet.boc │ │ ├── burn-token.fif │ │ ├── send-token.fif │ │ ├── print-hex.fif │ │ ├── new-wton.fif │ │ ├── jetton-minter.fif │ │ └── jetton-minter-ICO.fif │ ├── params.fc │ ├── op-codes.fc │ ├── compile.sh │ ├── jetton-utils.fc │ ├── jetton-minter.fc │ ├── jetton-minter-ICO.fc │ └── jetton-wallet.fc ├── nft │ ├── params.fc │ ├── web-example │ │ ├── my_collection.json │ │ ├── my_nft.json │ │ └── index.html │ ├── op-codes.fc │ ├── build │ │ ├── print-hex.fif │ │ └── nft-marketplace-code.fif │ ├── compile.sh │ ├── nft-marketplace.fc │ ├── nft-item.fc │ ├── nft-sale.fc │ ├── nft-collection.fc │ ├── nft-collection-editable.fc │ └── nft-item-editable-DRAFT.fc ├── misc │ └── forward-fee-calc.fc ├── README.md └── stdlib.fc ├── .gitignore ├── config.json ├── fift-libs ├── Color.fif ├── Fift.fif ├── GetOpt.fif ├── Lists.fif ├── Stack.fif └── TonUtil.fif ├── deploy-wallet.py ├── deploy-item.py ├── LICENSE ├── deploy-collection.py └── README.md /pyfift/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyfift/base/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyfift/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyfift/wallet/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /token-contract/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | tmp/ 3 | .vscode/ 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class -------------------------------------------------------------------------------- /token-contract/ft/build/wton.addr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anomaly-guard/nft-deployer/HEAD/token-contract/ft/build/wton.addr -------------------------------------------------------------------------------- /token-contract/ft/build/jetton-wallet.boc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anomaly-guard/nft-deployer/HEAD/token-contract/ft/build/jetton-wallet.boc -------------------------------------------------------------------------------- /pyfift/nft/utils.py: -------------------------------------------------------------------------------- 1 | def chunker(lst, n): 2 | """Yield successive n-sized chunks from lst.""" 3 | for i in range(0, len(lst), n): 4 | yield lst[i:i + n] -------------------------------------------------------------------------------- /token-contract/ft/params.fc: -------------------------------------------------------------------------------- 1 | int workchain() asm "0 PUSHINT"; 2 | 3 | () force_chain(slice addr) impure { 4 | (int wc, _) = parse_std_addr(addr); 5 | throw_unless(333, wc == workchain()); 6 | } -------------------------------------------------------------------------------- /token-contract/nft/params.fc: -------------------------------------------------------------------------------- 1 | int workchain() asm "0 PUSHINT"; 2 | 3 | () force_chain(slice addr) impure { 4 | (int wc, _) = parse_std_addr(addr); 5 | throw_unless(333, wc == workchain()); 6 | } -------------------------------------------------------------------------------- /token-contract/ft/build/burn-token.fif: -------------------------------------------------------------------------------- 1 | "Asm.fif" include 2 | "TonUtil.fif" include 3 | smca 2drop Addr, 5 | b> 6 | 2 boc+>B 7 | 8 | "burn-token.boc" B>file 9 | 10 | 11 | -------------------------------------------------------------------------------- /token-contract/ft/build/send-token.fif: -------------------------------------------------------------------------------- 1 | "Asm.fif" include 2 | "TonUtil.fif" include 3 | smca 2drop Addr, 5 | "EQDkZIvi6fkgNVxLOgw5hzquGhxhUvhvJ4B836p-NQ-iLKCv" $>smca 2drop Addr, 6 | 0 1 u, 7 | 1 Gram, 8 | 0 1 u, 9 | b> 10 | 11 | -------------------------------------------------------------------------------- /token-contract/ft/op-codes.fc: -------------------------------------------------------------------------------- 1 | int op::transfer() asm "0xf8a7ea5 PUSHINT"; 2 | int op::transfer_notification() asm "0x7362d09c PUSHINT"; 3 | int op::internal_transfer() asm "0x178d4519 PUSHINT"; 4 | int op::excesses() asm "0xd53276db PUSHINT"; 5 | int op::burn() asm "0x595f07bc PUSHINT"; 6 | int op::burn_notification() asm "0x7bdd97de PUSHINT"; 7 | 8 | ;; Minter 9 | int op::mint() asm "21 PUSHINT"; 10 | -------------------------------------------------------------------------------- /token-contract/ft/build/print-hex.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | ."jetton-minter:" cr 6 | 7 | "jetton-minter.fif" include 8 | 2 boc+>B dup Bx. cr 9 | 10 | ."jetton-wallet:" cr 11 | 12 | "jetton-wallet.fif" include 13 | 2 boc+>B dup Bx. cr 14 | 15 | 16 | ."jetton-minter-ICO:" cr 17 | 18 | "jetton-minter-ICO.fif" include 19 | 2 boc+>B dup Bx. cr 20 | -------------------------------------------------------------------------------- /token-contract/ft/compile.sh: -------------------------------------------------------------------------------- 1 | func -SPA -o ./build/jetton-wallet.fif ../stdlib.fc params.fc op-codes.fc jetton-utils.fc jetton-wallet.fc 2 | echo '"build/jetton-wallet.fif" include 2 boc+>B "build/jetton-wallet.boc" B>file' | fift -s 3 | func -SPA -o ./build/jetton-minter.fif ../stdlib.fc params.fc op-codes.fc jetton-utils.fc jetton-minter.fc 4 | func -SPA -o ./build/jetton-minter-ICO.fif ../stdlib.fc params.fc op-codes.fc jetton-utils.fc jetton-minter-ICO.fc 5 | 6 | fift -s build/print-hex.fif 7 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "testnet", 3 | "private_key": { 4 | "hex": null 5 | }, 6 | "fift": { 7 | "libs": "fift-libs" 8 | }, 9 | "lite-client": { 10 | "config":{ 11 | "mainnet": "tmp/global.config.json", 12 | "testnet": "tmp/testnet-global.config.json" 13 | } 14 | }, 15 | "contract-codes":{ 16 | "nft":{ 17 | "collection": "token-contract/nft/build/nft-collection-code.fif", 18 | "item": "token-contract/nft/build/nft-item-code.fif" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pyfift/core/boc.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | 4 | class BoC: 5 | def __init__(self, data, format="hex") -> None: 6 | self.data = data 7 | self.foramt = format 8 | 9 | def b64(self): 10 | return base64.b64encode(self.bytes()) 11 | 12 | def hex(self): 13 | return self.data 14 | 15 | def bytes(self): 16 | boc_bytes = bytes.fromhex(self.data) 17 | return boc_bytes 18 | 19 | def write(self, out): 20 | with open(out, "wb") as f: 21 | f.write(self.bytes()) -------------------------------------------------------------------------------- /token-contract/nft/web-example/my_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Marketplace Creatures", 3 | "description": "Marketplace Creatures are adorable aquatic beings primarily for demonstrating what can be done using the Marketplace platform. Adopt one today to try out all the Marketplace buying, selling, and bidding feature set.", 4 | "image": "https://ton.org/img/duck.svg", 5 | "external_link": "https://matketplacecreatures.io", 6 | "seller_fee_basis_points": 100, 7 | "fee_recipient": "0xA97F337c39cccE66adfeCB2BF99C1DdC54C2D721" 8 | } -------------------------------------------------------------------------------- /fift-libs/Color.fif: -------------------------------------------------------------------------------- 1 | library Color 2 | { 27 emit } : esc 3 | { char " word 27 chr swap $+ 1 ' type does create } :_ make-esc" 4 | make-esc"[0m" ^reset 5 | make-esc"[30m" ^black 6 | make-esc"[31m" ^red 7 | make-esc"[32m" ^green 8 | make-esc"[33m" ^yellow 9 | make-esc"[34m" ^blue 10 | make-esc"[35m" ^magenta 11 | make-esc"[36m" ^cyan 12 | make-esc"[37m" ^white 13 | // bold 14 | make-esc"[30;1m" ^Black 15 | make-esc"[31;1m" ^Red 16 | make-esc"[32;1m" ^Green 17 | make-esc"[33;1m" ^Yellow 18 | make-esc"[34;1m" ^Blue 19 | make-esc"[35;1m" ^Magenta 20 | make-esc"[36;1m" ^Cyan 21 | make-esc"[37;1m" ^White 22 | -------------------------------------------------------------------------------- /token-contract/nft/op-codes.fc: -------------------------------------------------------------------------------- 1 | int op::transfer() asm "0x5fcc3d14 PUSHINT"; 2 | int op::ownership_assigned() asm "0x05138d91 PUSHINT"; 3 | int op::excesses() asm "0xd53276db PUSHINT"; 4 | int op::get_static_data() asm "0x2fcb26a2 PUSHINT"; 5 | int op::report_static_data() asm "0x8b771735 PUSHINT"; 6 | int op::get_royalty_params() asm "0x693d3950 PUSHINT"; 7 | int op::report_royalty_params() asm "0xa8cb00ad PUSHINT"; 8 | 9 | ;; NFTEditable 10 | int op::edit_content() asm "0x1a0b9d51 PUSHINT"; 11 | int op::transfer_editorship() asm "0x1c04412a PUSHINT"; 12 | int op::editorship_assigned() asm "0x511a4463 PUSHINT"; 13 | -------------------------------------------------------------------------------- /token-contract/nft/build/print-hex.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | ."nft-collection:" cr 6 | 7 | "nft-collection-code.fif" include 8 | 2 boc+>B dup Bx. cr cr 9 | 10 | ."nft-collection-editable:" cr 11 | 12 | "nft-collection-editable-code.fif" include 13 | 2 boc+>B dup Bx. cr cr 14 | 15 | ."nft:" cr 16 | 17 | "nft-item-code.fif" include 18 | 2 boc+>B dup Bx. cr cr 19 | 20 | ."nft-marketplace:" cr 21 | 22 | "nft-marketplace-code.fif" include 23 | 2 boc+>B dup Bx. cr cr 24 | 25 | ."nft-sale:" cr 26 | 27 | "nft-sale-code.fif" include 28 | 2 boc+>B dup Bx. cr cr 29 | -------------------------------------------------------------------------------- /pyfift/base/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from pyfift.base.fift import Fift 4 | from pyfift.base.lite_client import LiteClient 5 | from pyfift.base.keypair import KeyPair 6 | 7 | 8 | class App: 9 | fift: Fift 10 | lite_client: LiteClient 11 | key: KeyPair 12 | 13 | @classmethod 14 | def init(cls, config="config.json"): 15 | cls.config = json.loads(open(config).read()) 16 | # Load Fift 17 | libs = cls.config["fift"]["libs"] 18 | cls.fift = Fift(libs) 19 | # Load lite_client 20 | l_config = cls.config["lite-client"]["config"][cls.config["network"]] 21 | cls.lite_client = LiteClient(l_config) 22 | # Load Keys 23 | hex_key = cls.config["private_key"]["hex"] 24 | cls.key = KeyPair(hex_key) 25 | -------------------------------------------------------------------------------- /pyfift/core/code.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.fift import Fift 2 | from pyfift.core.boc import BoC 3 | 4 | 5 | class ContractCode: 6 | build_code = ''' 7 | "Asm.fif" include 8 | "TonUtil.fif" include 9 | "%file_address%" include 2 boc+>B constant code_boc 10 | 11 | "result boc:{" type code_boc Bx. "}" type 12 | ''' 13 | 14 | def __init__(self, compiled_code_path): 15 | self.compiled_code_path = compiled_code_path 16 | 17 | def to_boc(self): 18 | outs = Fift().run(self.build_code, { 19 | "file_address": self.compiled_code_path, 20 | }, ["result boc"]) 21 | boc = outs["result boc"] 22 | return BoC(boc) 23 | 24 | 25 | def code_boc(file: str): 26 | return ContractCode(file).to_boc() 27 | -------------------------------------------------------------------------------- /token-contract/nft/compile.sh: -------------------------------------------------------------------------------- 1 | rm build/nft-item-code.fif 2 | rm build/nft-item-editable-code.fif 3 | rm build/nft-collection-code.fif 4 | rm build/nft-collection-editable-code.fif 5 | rm build/nft-marketplace-code.fif 6 | rm build/nft-sale-code.fif 7 | 8 | func -o build/nft-item-code.fif -SPA ../stdlib.fc params.fc op-codes.fc nft-item.fc 9 | func -o build/nft-item-editable-code.fif -SPA ../stdlib.fc params.fc op-codes.fc nft-item-editable-DRAFT.fc 10 | func -o build/nft-collection-code.fif -SPA ../stdlib.fc params.fc op-codes.fc nft-collection.fc 11 | func -o build/nft-collection-editable-code.fif -SPA ../stdlib.fc params.fc op-codes.fc nft-collection-editable.fc 12 | func -o build/nft-marketplace-code.fif -SPA ../stdlib.fc op-codes.fc nft-marketplace.fc 13 | func -o build/nft-sale-code.fif -SPA ../stdlib.fc op-codes.fc nft-sale.fc 14 | 15 | fift -s build/print-hex.fif 16 | -------------------------------------------------------------------------------- /token-contract/nft/web-example/my_nft.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes" : [ 3 | { 4 | "trait_type" : "level", 5 | "value" : 3 6 | }, 7 | { 8 | "trait_type" : "stamina", 9 | "value" : 11.7 10 | }, 11 | { 12 | "trait_type" : "personality", 13 | "value" : "sleepy" 14 | }, 15 | { 16 | "display_type" : "boost_number", 17 | "trait_type" : "aqua_power", 18 | "value" : 30 19 | }, 20 | { 21 | "display_type" : "boost_percentage", 22 | "trait_type" : "stamina_increase", 23 | "value" : 15 24 | }, 25 | { 26 | "display_type" : "number", 27 | "trait_type" : "generation", 28 | "value" : 1 29 | } 30 | ], 31 | "description" : "Friendly Marketplace Creature that enjoys long swims in the ocean.", 32 | "external_url" : "https://example.com/?token_id=2", 33 | "image" : "https://ton.org/img/img_1.svg", 34 | "name" : "Boris McCoy" 35 | } -------------------------------------------------------------------------------- /deploy-wallet.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.app import App 2 | from pyfift.wallet.wallet_v3_r2 import WalletV3R2 3 | 4 | 5 | App.init() 6 | 7 | wallet = WalletV3R2() 8 | wallet.init_data() 9 | addr = wallet.address(binary=False) 10 | state = App.lite_client.state(addr) 11 | if state["state"] == "empty": 12 | print("This account is fresh ...") 13 | print("Wallet Address:", addr) 14 | print("Please send some TONs to the address to deploy it later") 15 | elif state["state"] == "active": 16 | print("This account is already active and deployed ...") 17 | print("Wallet Address:", addr) 18 | print("Balance: %.5f TON" % (state["balance"] / (10 ** 9))) 19 | elif state["state"] == "inactive": 20 | min_balance = 50000000 # .05 TONs 21 | if state["balance"] < min_balance: 22 | print("Please send at least 0.2TONs to proceed for deployment ...") 23 | print("This account is inactive and ready to deploy wallet contract") 24 | wallet.prepare_deploy(value=0, external=True) 25 | wallet.deploy() 26 | -------------------------------------------------------------------------------- /token-contract/ft/build/new-wton.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | 6 | "jetton-wallet.boc" file>B B>boc =: ft_code 7 | 8 | <{ 9 | SETCP0 ACCEPT 10 | now PUSHINT 11 | "jetton-minter.fif" include PUSHREF SETCODE 12 | }>s =: contract_code 13 | 14 | =: contract_storage 15 | 16 | 0 =: wc 17 | 18 | ref, contract_storage ref, b> 19 | dup =: state_init 20 | dup hashu wc swap 2constant contract_addr 21 | 22 | ."Contract address = " contract_addr .addr cr 23 | 24 | contract_addr "wton.addr" save-address-verbose 25 | 26 | ."Non-bounceable address (for init): " contract_addr 7 .Addr cr 27 | ."Bounceable address (for later access): " contract_addr 6 .Addr cr 28 | 29 | 30 | =: init_message 31 | 32 | 33 | 34 | 2 boc+>B 35 | "wton-query.boc" tuck B>file 36 | ."(Saved collection contract creating query to file " type .")" cr 37 | -------------------------------------------------------------------------------- /deploy-item.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from pyfift.base.app import App 4 | from pyfift.wallet.wallet_v3_r2 import WalletV3R2 5 | from pyfift.nft.nft_deploy import DeployNFTMessage 6 | 7 | 8 | 9 | App.init() 10 | 11 | wallet = WalletV3R2() 12 | wallet.init_data() 13 | addr = wallet.address(binary=False) 14 | state = App.lite_client.state(addr) 15 | 16 | if state["state"] != "active": 17 | if state["state"] == "empty": print("Empty account, send some TONs and deploy it ...") 18 | elif state["state"] == "inactive": print("Deploy the wallet contract before proceeding ...") 19 | sys.exit() 20 | 21 | if state["balance"] < 50000000: 22 | print("insufficient balance for sending nft item message, min: 0.05 TON") 23 | sys.exit() 24 | 25 | 26 | nft_collection_addr = "kQCtSPsG5JlnAeviQAzc811k2X4aRbHm1OJmzUnpkNNoPgLJ" 27 | msg_body = DeployNFTMessage( 28 | index=0, 29 | content_url="my_nft.json", 30 | amount=50000000, 31 | owner=addr, 32 | ).to_boc() 33 | wallet.send_to_contract(msg_body, 50000000, nft_collection_addr) -------------------------------------------------------------------------------- /token-contract/misc/forward-fee-calc.fc: -------------------------------------------------------------------------------- 1 | {- 2 | Forward fee calculation supporting different workchains 3 | -} 4 | 5 | ;; See crypto/block/transaction.cpp:L1499 6 | int msg_fwd_fee(slice destination, cell message_body, cell init_state, int max_viewed_cells) inline { 7 | (int wc, _) = parse_std_addr(destination); 8 | throw_unless(107, (workchain == -1) | (workchain == 0) ); 9 | int config_index = 25 + workchain; 10 | slice cfg = config_param(config_index).begin_parse().skip_bits(8); 11 | int lump_price = cfg~load_uint(64); 12 | int bit_price = cfg~load_uint(64); 13 | int cell_price = cfg~load_uint(64); 14 | (int cells, int bits, _) = compute_data_size(message_body, max_viewed_cells); 15 | cells -= 1; 16 | bits -= message_body.slice_bits(); 17 | 18 | (int is_cells, int is_bits, _) = compute_data_size(init_state, max_viewed_cells); 19 | is_cells -= 1; 20 | is_bits -= init_state.slice_bits(); 21 | return lump_price + (((bits + is_bits) * bit_price + (cells + is_cells) * cell_price + 65535) >> 16 ); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /pyfift/nft/content.py: -------------------------------------------------------------------------------- 1 | from pyfift.nft.utils import chunker 2 | 3 | # tail#_ {bn:#} b:(bits bn) = SnakeData ~0; 4 | # cons#_ {bn:#} {n:#} b:(bits bn) next:^(SnakeData ~n) = SnakeData ~(n + 1); 5 | class SnakeData: 6 | bits: bytes 7 | ref: "SnakeData" 8 | 9 | def __init__(self, bits=None, ref=None): 10 | self.bits = bits 11 | self.ref = ref 12 | 13 | def encode(self): 14 | c = "" 15 | bits = "x{%s} s," % self.bits.hex() 16 | next_ = "" 17 | if self.ref is not None: 18 | t = self.ref.encode() 19 | next_ = "%s ref," % t 20 | return c.format(bits=bits, next=next_) 21 | 22 | @staticmethod 23 | def build(chunks): 24 | if len(chunks) == 0: 25 | return None 26 | c = chunks[0] 27 | return SnakeData(bits=c, ref=SnakeData.build(chunks[1:])) 28 | 29 | def build_text_obj(url): 30 | b = bytearray(url.encode("ascii")) 31 | max_cell_size = 1023 // 8 32 | chunks_ = list(chunker(b, max_cell_size)) 33 | return SnakeData.build(chunks_) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Raven 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyfift/wallet/wallet_base.py: -------------------------------------------------------------------------------- 1 | from pyfift.core.boc import BoC 2 | from pyfift.base.app import App 3 | from pyfift.core.internal_msg import InternalMessage 4 | 5 | 6 | class WalletBase: 7 | def seq_no(self) -> int: 8 | pass 9 | 10 | def build_message(self, sub_wallet: int, valid_until: int, seq_no: int, message: bytes | BoC): 11 | pass 12 | 13 | def transact(self, sub_wallet: int, valid_until: int, seq_no: int, message: bytes | BoC, message_mode: int = 64): 14 | msg, params = self.build_message(sub_wallet, valid_until, seq_no, message, message_mode=message_mode) 15 | outs = App.fift.run(msg, params, ["result boc"]) 16 | boc = outs["result boc"] 17 | return BoC(boc) 18 | 19 | def send_to_contract(self, msg_body: BoC | bytes, value: int, dst: str, sub_wallet=0, valid_until=-1, mode=64): 20 | m = InternalMessage(msg_body, dst, value).to_boc() 21 | seq_no = self.seq_no() 22 | msg = self.transact(sub_wallet, valid_until, seq_no, m, mode) 23 | s = App.lite_client.send_boc(msg) 24 | if s: 25 | print("Message successfully sent to lite-servers!") -------------------------------------------------------------------------------- /pyfift/base/keypair.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.fift import Fift 2 | import secrets 3 | 4 | 5 | class KeyPair: 6 | deriv_pub_key = ''' 7 | B{%priv_key%} priv>pub 8 | "public_key:{" type Bx. "}" type 9 | ''' 10 | 11 | def __init__(self, priv_key=None) -> None: 12 | if priv_key: 13 | if len(priv_key) == 64: 14 | print("using existing key defined in config ...") 15 | self.priv_key = priv_key 16 | else: 17 | raise RuntimeError("only 32 bytes hex strings are supported as private keys") 18 | else: 19 | print("no key is provided, generating key...") 20 | self.priv_key = secrets.token_hex(32).upper() 21 | print("private key: %s" % self.priv_key) 22 | print("please save your private key for later access, ideally in config.json") 23 | self.pub_key = self._deriv_pub() 24 | if not priv_key: 25 | print("public key: %s" % self.pub_key) 26 | 27 | 28 | def _deriv_pub(self): 29 | outs = Fift().run(self.deriv_pub_key, { 30 | "priv_key": self.priv_key 31 | }, ["public_key"]) 32 | k = outs["public_key"] 33 | return k 34 | 35 | -------------------------------------------------------------------------------- /pyfift/core/state_init.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.fift import Fift 2 | from pyfift.core.boc import BoC 3 | 4 | 5 | class StateInit: 6 | build_state_init = ''' 7 | "Asm.fif" include 8 | "TonUtil.fif" include 9 | B{%code%} B>boc 10 | constant contract_code 11 | 12 | B{%data%} B>boc 13 | constant contract_data 14 | 15 | // State Init 16 | 17 | 2 boc+>B 18 | 19 | "result boc:{" type Bx. "}" type 20 | ''' 21 | 22 | # _ split_depth:(Maybe (## 5)) special:(Maybe TickTock) 23 | # code:(Maybe ^Cell) data:(Maybe ^Cell) 24 | # library:(HashmapE 256 SimpleLib) = StateInit; 25 | def __init__(self, code: bytes | BoC=None, data: bytes | BoC=None): 26 | self.code = code 27 | self.data = data 28 | if isinstance(self.code, BoC): 29 | self.code = self.code.hex() 30 | if isinstance(self.data, BoC): 31 | self.data = self.data.hex() 32 | 33 | def to_boc(self): 34 | outs = Fift().run(self.build_state_init, { 35 | "code": self.code, 36 | "data": self.data, 37 | }, ["result boc"]) 38 | boc = outs["result boc"] 39 | return BoC(boc) 40 | -------------------------------------------------------------------------------- /pyfift/nft/nft_deploy.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.fift import Fift 2 | from pyfift.core.boc import BoC 3 | from pyfift.nft.content import build_text_obj 4 | 5 | 6 | class DeployNFTMessage: 7 | build_deploy_msg = ''' 8 | "Asm.fif" include 9 | "TonUtil.fif" include 10 | "%owner_addr%" $>smca 2drop 2constant my_addr 11 | 12 | %snake_content% constant snake_cell 13 | constant content_cell 14 | constant msg_cell 15 | constant msg_body 16 | msg_body 2 boc+>B 17 | "result boc:{" type Bx. "}" type 18 | ''' 19 | 20 | def __init__(self, index=0, content_url='', amount=0, owner=''): 21 | self.content_url = content_url 22 | self.index = index 23 | self.amount = amount 24 | self.owner = owner 25 | 26 | def to_boc(self): 27 | outs = Fift().run(self.build_deploy_msg, { 28 | "amount": self.amount, 29 | "owner_addr": self.owner, 30 | "index": self.index, 31 | "snake_content": build_text_obj(self.content_url).encode(), 32 | }, ["result boc"]) 33 | boc = outs["result boc"] 34 | return BoC(boc) 35 | -------------------------------------------------------------------------------- /deploy-collection.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from pyfift.base.app import App 4 | from pyfift.wallet.wallet_v3_r2 import WalletV3R2 5 | from pyfift.nft.nft_collection import NftCollection 6 | 7 | 8 | App.init() 9 | 10 | wallet = WalletV3R2() 11 | wallet.init_data() 12 | addr = wallet.address(binary=False) 13 | state = App.lite_client.state(addr) 14 | 15 | if state["state"] != "active": 16 | if state["state"] == "empty": print("Empty account, send some TONs and deploy it ...") 17 | elif state["state"] == "inactive": print("Deploy the wallet contract before proceeding ...") 18 | sys.exit() 19 | 20 | if state["balance"] < 50000000: 21 | print("insufficient balance for deploying nft contract, min: 0.05 TON") 22 | sys.exit() 23 | 24 | collection = NftCollection() 25 | collection.init_data( 26 | owner=addr, 27 | royalty_factor=5, 28 | royalty_base=100, 29 | next_item_index=0, 30 | collection_content_url='https://raw.githubusercontent.com/ton-blockchain/token-contract/main/nft/web-example/my_collection.json', 31 | common_content_url='https://raw.githubusercontent.com/ton-blockchain/token-contract/main/nft/web-example/', 32 | ) 33 | collection.prepare_deploy(value=0.05, external=False) 34 | print("preparing to deploy nft collection contract ...") 35 | print("NFT Collection address:", collection.h_addr) 36 | collection.deploy(wallet, mode=64 + 3) -------------------------------------------------------------------------------- /token-contract/ft/jetton-utils.fc: -------------------------------------------------------------------------------- 1 | cell pack_jetton_wallet_data(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { 2 | return begin_cell() 3 | .store_coins(balance) 4 | .store_slice(owner_address) 5 | .store_slice(jetton_master_address) 6 | .store_ref(jetton_wallet_code) 7 | .end_cell(); 8 | } 9 | 10 | cell calculate_jetton_wallet_state_init(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { 11 | return begin_cell() 12 | .store_uint(0, 2) 13 | .store_dict(jetton_wallet_code) 14 | .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) 15 | .store_uint(0, 1) 16 | .end_cell(); 17 | } 18 | 19 | slice calculate_jetton_wallet_address(cell state_init) inline { 20 | return begin_cell().store_uint(4, 3) 21 | .store_int(workchain(), 8) 22 | .store_uint(cell_hash(state_init), 256) 23 | .end_cell() 24 | .begin_parse(); 25 | } 26 | 27 | slice calculate_user_jetton_wallet_address(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { 28 | return calculate_jetton_wallet_address(calculate_jetton_wallet_state_init(owner_address, jetton_master_address, jetton_wallet_code)); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /pyfift/base/fift.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import re 4 | 5 | 6 | class Fift: 7 | def __init__(self, libs_path="./fift-libs/"): 8 | self.libs_path = libs_path 9 | 10 | @staticmethod 11 | def _format_fift(code: str, args: dict) -> str: 12 | for k, w in args.items(): 13 | code = code.replace(f"%{k}%", str(w)) 14 | return code 15 | 16 | @staticmethod 17 | def _str_to_pipe(data: str) -> int: 18 | read, write = os.pipe() 19 | os.write(write, data.encode()) 20 | os.close(write) 21 | return read 22 | 23 | def run(self, code, params=None, outputs=None) -> dict: 24 | if params is None: 25 | params = {} 26 | code = Fift._format_fift(code, params) 27 | in_ = Fift._str_to_pipe(code) 28 | p = subprocess.Popen(f"fift -I {self.libs_path}", stdin=in_, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 29 | out, err = p.communicate() 30 | out, err = out.decode("utf-8"), err.decode("utf-8") 31 | r_code = p.returncode 32 | if err.strip() != '': 33 | print(err) 34 | raise RuntimeError("Fift error: ") 35 | if r_code != 0: 36 | raise RuntimeError("Non successful exit code") 37 | values = {} 38 | if outputs is None: 39 | return values 40 | for tag in outputs: 41 | s = re.compile(rf"{tag}:{{(.*?)}}").findall(out) 42 | values[tag] = s[0] 43 | return values 44 | 45 | -------------------------------------------------------------------------------- /token-contract/README.md: -------------------------------------------------------------------------------- 1 | # Fungible, Non-Fungible, Semi-Fungible Tokens Smart Contracts 2 | 3 | ## NFT (Non-Fungible tokens) in `nft` folder 4 | 5 | Basic implementation of smart contracts for NFT tokens and NFT collections in accordance with the [Standard](https://github.com/ton-blockchain/TIPs/issues/62). 6 | 7 | `nft-collection.fc` - basic implementation of immutable NFT collection with royalty. 8 | 9 | `nft-collection-editable.fc` - basic implementation of the NFT collection with royalty in which the author can change the content and royalty params. 10 | 11 | It is preferable to use an editable collection in case if you decide to change content hosting in the future (for example, to TON Storage). 12 | 13 | `nft-item.fc` - basic implementation of immutable NFT item. 14 | 15 | [TonWeb](https://github.com/toncenter/tonweb) JavaScript SDK 0.0.38+ supports these contracts. 16 | 17 | Also repo contains an example of a simple marketplace smart contract `nft-marketplace` and a smart contract for selling NFT for a fixed price for Toncoins `nft-sale`. 18 | 19 | In a real product, marketplace and sale smart contracts are likely to be more sophisticated. 20 | 21 | ## Jettons (Fungible tokens) in `ft` folder 22 | 23 | Basic implementation of smart contracts for Jetton wallet and Jetton minter in accordance with the [Standard](https://github.com/ton-blockchain/TIPs/issues/74). 24 | 25 | Contains an example of a simple ICO smart contract. 26 | 27 | ## Semi-Fungible 28 | 29 | Semi-Fungible tokens is combination of NFT and FT. -------------------------------------------------------------------------------- /pyfift/core/contract_address.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.app import App 2 | from pyfift.base.fift import Fift 3 | from pyfift.core.boc import BoC 4 | 5 | class ContractAddress: 6 | build_contract_address = ''' 7 | "Asm.fif" include 8 | "TonUtil.fif" include 9 | B{%state_init%} B>boc 10 | constant state_init 11 | 0 constant work_chain 12 | state_init hashu constant state_hash 13 | // Contract Address 14 | constant addr_cell 15 | addr_cell 2 boc+>B constant b_addr 16 | 17 | work_chain state_hash %network_mode_flag% smca>$ constant h_addr 18 | 19 | "result boc:{" type b_addr Bx. "}" type 20 | "human readable:{" type h_addr type "}" type 21 | ''' 22 | 23 | # _ split_depth:(Maybe (## 5)) special:(Maybe TickTock) 24 | # code:(Maybe ^Cell) data:(Maybe ^Cell) 25 | # library:(HashmapE 256 SimpleLib) = StateInit; 26 | def __init__(self, state_init: bytes | BoC=None): 27 | self.state_init = state_init 28 | if isinstance(self.state_init, BoC): 29 | self.state_init = self.state_init.hex() 30 | 31 | def to_boc(self): 32 | outs = Fift().run(self.build_contract_address, { 33 | "state_init": self.state_init, 34 | "network_mode_flag": 0 if App.config["network"] == "mainnet" else 2, 35 | }, ["result boc"]) 36 | boc = outs["result boc"] 37 | return BoC(boc) 38 | 39 | def human(self): 40 | outs = Fift().run(self.build_contract_address, { 41 | "state_init": self.state_init, 42 | "network_mode_flag": 0 if App.config["network"] == "mainnet" else 2, 43 | }, ["result boc", "human readable"]) 44 | return outs["human readable"] 45 | -------------------------------------------------------------------------------- /token-contract/nft/nft-marketplace.fc: -------------------------------------------------------------------------------- 1 | ;; NFT marketplace smart contract 2 | 3 | ;; storage scheme 4 | ;; storage#_ owner_address:MsgAddress 5 | ;; = Storage; 6 | 7 | (slice) load_data() inline { 8 | var ds = get_data().begin_parse(); 9 | return 10 | (ds~load_msg_addr() ;; owner 11 | ); 12 | } 13 | 14 | () save_data(slice owner_address) impure inline { 15 | set_data(begin_cell() 16 | .store_slice(owner_address) 17 | .end_cell()); 18 | } 19 | 20 | () recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { 21 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 22 | return (); 23 | } 24 | slice cs = in_msg_full.begin_parse(); 25 | int flags = cs~load_uint(4); 26 | 27 | if (flags & 1) { ;; ignore all bounced messages 28 | return (); 29 | } 30 | slice sender_address = cs~load_msg_addr(); 31 | 32 | var (owner_address) = load_data(); 33 | throw_unless(401, equal_slices(sender_address, owner_address)); 34 | int op = in_msg_body~load_uint(32); 35 | 36 | if (op == 1) { ;; deploy new auction 37 | int amount = in_msg_body~load_coins(); 38 | (cell state_init, cell body) = (cs~load_ref(), cs~load_ref()); 39 | int state_init_hash = cell_hash(state_init); 40 | slice dest_address = begin_cell().store_int(0, 8).store_uint(state_init_hash, 256).end_cell().begin_parse(); 41 | 42 | var msg = begin_cell() 43 | .store_uint(0x18, 6) 44 | .store_uint(4, 3).store_slice(dest_address) 45 | .store_grams(amount) 46 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 47 | .store_ref(state_init) 48 | .store_ref(body); 49 | send_raw_message(msg.end_cell(), 1); ;; paying fees, revert on errors 50 | } 51 | } 52 | 53 | () recv_external(slice in_msg) impure { 54 | } -------------------------------------------------------------------------------- /pyfift/core/contract.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.app import App 2 | from pyfift.core.code import ContractCode 3 | from pyfift.core.state_init import StateInit 4 | from pyfift.core.deploy_contract import DeployContract 5 | from pyfift.core.contract_address import ContractAddress 6 | from pyfift.wallet.wallet_base import WalletBase 7 | 8 | 9 | class Contract: 10 | def init_code(self, code_path: str=None, code_hex: str=None): 11 | if code_path and code_hex: 12 | raise RuntimeError("Just one of code parameters can be specificed") 13 | if code_path: 14 | self.code = ContractCode(code_path).to_boc() 15 | if code_hex: 16 | self.code = code_hex 17 | 18 | def init_data(self, *args, **kwargs): 19 | self.data = None 20 | 21 | def address(self, binary=True): 22 | self.init = StateInit(self.code, self.data).to_boc() 23 | c = ContractAddress(self.init) 24 | self.addr = c.to_boc() 25 | self.h_addr = c.human() 26 | return self.addr if binary else self.h_addr 27 | 28 | def prepare_deploy(self, value: int = 0.01, external: bool = False, initial_msg=None): 29 | self.address() 30 | if initial_msg is None: 31 | initial_msg = self.initial_msg() 32 | self.msg = DeployContract(initial_msg, self.init, self.addr, App.key.priv_key, int(value * 10 ** 9)).to_boc(external=external) 33 | return self.msg 34 | 35 | def initial_msg(self): 36 | return None 37 | 38 | def deploy(self, wallet: WalletBase=None, sub_wallet=0, valid_until=-1, mode=64): 39 | if wallet: 40 | seq_no = wallet.seq_no() 41 | msg = wallet.transact(sub_wallet, valid_until, seq_no, self.msg, mode) 42 | s = App.lite_client.send_boc(msg) 43 | if s: 44 | print("Deploy message successfully sent to lite-servers!") 45 | else: 46 | # Direct deploy 47 | App.lite_client.send_boc(self.msg) 48 | -------------------------------------------------------------------------------- /pyfift/core/internal_msg.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.fift import Fift 2 | from pyfift.core.boc import BoC 3 | 4 | 5 | class InternalMessage: 6 | build_src_addr = ''' 7 | "%src%" $>smca 2drop 2constant src_addr 8 | boc constant msg_body 14 | 15 | %build_src% 16 | constant src_addr_s 17 | 18 | "%destination%" $>smca 2drop 2constant my_addr 19 | '0' 21 | // disable ihr, allow bounces and is not bounced itself => '010' 22 | // src addr_none => '00' 23 | b{0010} s, 24 | src_addr_s s, 25 | my_addr Addr, 26 | %value% Gram, 27 | 0 1 u, // currency dict 28 | 0 Gram, // ihr_fee 29 | 0 Gram, // fwd_fee 30 | 0 64 u, // created_lt 31 | 0 32 u, // created_at 32 | 0 1 u, // no init 33 | 1 1 u, // body ref 34 | msg_body ref, 35 | b> 36 | constant internal_msg 37 | 38 | internal_msg 2 boc+>B constant result 39 | 40 | "result boc:{" type result Bx. "}" type 41 | ''' 42 | # int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 43 | # src:MsgAddressInt dest:MsgAddressInt 44 | # value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 45 | # created_lt:uint64 created_at:uint32 = CommonMsgInfo; 46 | 47 | # message$_ {X:Type} info:CommonMsgInfo 48 | # init:(Maybe (Either StateInit ^StateInit)) 49 | # body:(Either X ^X) = Message X; 50 | def __init__(self, message: bytes | BoC, destination: str, value: int, src: str = None): 51 | self.message = message 52 | self.destination = destination 53 | self.src = src 54 | self.value = value 55 | if isinstance(self.message, BoC): 56 | self.message = self.message.hex() 57 | 58 | def to_boc(self): 59 | if self.src is None: 60 | self.src = "b{00}" 61 | else: 62 | self.src = self.build_src_addr.replace("%src%", self.src) 63 | outs = Fift().run(self.build_msg, { 64 | "message": self.message, 65 | "destination": self.destination, 66 | "build_src": self.src, 67 | "value": self.value, 68 | }, ["result boc"]) 69 | boc = outs["result boc"] 70 | return BoC(boc) 71 | -------------------------------------------------------------------------------- /pyfift/nft/nft_collection.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.app import App 2 | from pyfift.base.fift import Fift 3 | from pyfift.core.boc import BoC 4 | from pyfift.core.code import code_boc 5 | from pyfift.core.contract import Contract 6 | from pyfift.nft.content import build_text_obj 7 | 8 | 9 | class NftCollection(Contract): 10 | build_collection_storage = ''' 11 | "Asm.fif" include 12 | "TonUtil.fif" include 13 | 14 | B{%item_code%} B>boc 15 | constant nft_item_code 16 | 17 | "%wallet_addr%" $>smca 2drop 2constant my_addr 18 | 19 | // Royalty Cell 20 | constant royalty_cell 21 | 22 | // Contents 23 | %gen_collection_content% constant collection_content 24 | %gen_common_content% constant common_content 25 | 26 | // Content Cells 27 | constant collection_content_c 28 | constant common_content_c 29 | constant contents_cell 30 | 31 | // Storage Cell 32 | 33 | 34 | 2 boc+>B 35 | "result boc:{" type Bx. "}" type 36 | ''' 37 | def __init__(self) -> None: 38 | self.item_code = code_boc(App.config["contract-codes"]["nft"]["item"]).hex() 39 | self.init_code(code_path=App.config["contract-codes"]["nft"]["collection"]) 40 | 41 | def init_data(self, owner=None, royalty_factor=0, royalty_base=1, next_item_index=0, collection_content_url='', common_content_url=''): 42 | self.wallet_addr = owner 43 | self.royalty_factor = royalty_factor 44 | self.royalty_base = royalty_base 45 | self.next_item_index = next_item_index 46 | self.collection_content_url = collection_content_url 47 | self.common_content_url = common_content_url 48 | self.data = self._to_boc() 49 | 50 | def _to_boc(self): 51 | outs = Fift().run(self.build_collection_storage, { 52 | "item_code": self.item_code, 53 | "wallet_addr": self.wallet_addr, 54 | "royalty_factor": self.royalty_factor, 55 | "royalty_base": self.royalty_base, 56 | "next_item_index": self.next_item_index, 57 | "gen_collection_content": build_text_obj(self.collection_content_url).encode(), 58 | "gen_common_content": build_text_obj(self.common_content_url).encode(), 59 | }, ["result boc"]) 60 | boc = outs["result boc"] 61 | return BoC(boc) 62 | -------------------------------------------------------------------------------- /pyfift/base/lite_client.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from tempfile import NamedTemporaryFile 3 | 4 | from pyfift.core.boc import BoC 5 | 6 | 7 | class LiteClient: 8 | def __init__(self, config="./global.config.json"): 9 | self.config = config 10 | 11 | def _check_for_empty(self, msg): 12 | if "account state is empty" in msg: 13 | return True 14 | return False 15 | 16 | def _check_state(self, msg): 17 | if "state:(account_active" in msg: 18 | return 1 19 | elif "state:account_uninit" in msg: 20 | return 0 21 | return -1 22 | 23 | def _check_balance(self, msg: str): 24 | if "account balance is" in msg: 25 | first_d = "account balance is " 26 | i = msg.find(first_d) 27 | j = msg.find("ng", i) 28 | return int(msg[i + len(first_d):j]) 29 | return None 30 | 31 | def state(self, address): 32 | out, _ = self.run(self.config, f"getaccount {address}") 33 | if self._check_for_empty(out): 34 | return {"state": "empty"} 35 | state = self._check_state(out) 36 | if state == -1: 37 | raise RuntimeError("unknown account state") 38 | state = "active" if state == 1 else "inactive" 39 | balance = self._check_balance(out) 40 | return {"state": state, "balance": balance} 41 | 42 | def send_boc(self, boc): 43 | if isinstance(boc, BoC): 44 | boc = boc.bytes() 45 | f = NamedTemporaryFile(suffix=".boc", delete=False, mode="wb") 46 | f.write(boc) 47 | f.close() 48 | r_code, out, logs = self.run(self.config, f"sendfile {f.name}", throw=False) 49 | if r_code != 0: 50 | i = logs.find("sending query from file") 51 | b = logs.find("\n", i) 52 | e = logs.rfind("\n", 0, -2) 53 | print(logs[b+1:e]) 54 | raise RuntimeError("Encountered error processing boc") 55 | return "external message status is 1" in logs 56 | 57 | def get_method(self, method_id: int, addr: str, *args): 58 | if len(args) > 0: 59 | args = " " + " ".join(map(lambda x: str(x), args)) 60 | else: 61 | args = "" 62 | out, logs = self.run(self.config, f"runmethod {addr} {method_id}{args}") 63 | lines = out.split("\n") 64 | result_line = None 65 | for l in lines: 66 | if l.startswith("result:"): 67 | result_line = l 68 | break 69 | s = result_line.find("[") + 1 70 | e = result_line.rfind("]") 71 | return result_line[s:e] 72 | 73 | @classmethod 74 | def run(cls, config, command, throw=True): 75 | p = subprocess.Popen(f"lite-client -C {config} -c \"{command}\"", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 76 | out, err = p.communicate() 77 | # lite-client sends it logs to std::err 78 | out, logs = out.decode("utf-8"), err.decode("utf-8") 79 | r_code = p.returncode 80 | if r_code != 0 and throw: 81 | raise RuntimeError("Non successful exit code", logs) 82 | if not throw: 83 | return r_code, out, logs 84 | return out, logs 85 | -------------------------------------------------------------------------------- /token-contract/nft/build/nft-marketplace-code.fif: -------------------------------------------------------------------------------- 1 | "Asm.fif" include 2 | // automatically generated from `../stdlib.fc` `op-codes.fc` `nft-marketplace.fc` 3 | PROGRAM{ 4 | DECLPROC load_data 5 | DECLPROC save_data 6 | DECLPROC recv_internal 7 | DECLPROC recv_external 8 | load_data PROC:<{ 9 | // 10 | c4 PUSH // _1 11 | CTOS // ds 12 | LDMSGADDR // _6 _5 13 | DROP // _3 14 | }> 15 | save_data PROC:<{ 16 | // owner_address 17 | NEWC // owner_address _1 18 | SWAP // _1 owner_address 19 | STSLICER // _2 20 | ENDC // _3 21 | c4 POP 22 | }> 23 | recv_internal PROC:<{ 24 | // msg_value in_msg_full in_msg_body 25 | s2 POP // in_msg_body in_msg_full 26 | OVER // in_msg_body in_msg_full in_msg_body 27 | SEMPTY // in_msg_body in_msg_full _3 28 | IFJMP:<{ // in_msg_body in_msg_full 29 | 2DROP // 30 | }> // in_msg_body in_msg_full 31 | CTOS // in_msg_body cs 32 | 4 LDU // in_msg_body flags cs 33 | SWAP 34 | 1 PUSHINT // in_msg_body cs flags _10=1 35 | AND // in_msg_body cs _11 36 | IFJMP:<{ // in_msg_body cs 37 | 2DROP // 38 | }> // in_msg_body cs 39 | LDMSGADDR // in_msg_body sender_address cs 40 | load_data INLINECALLDICT // in_msg_body sender_address cs owner_address 41 | s1 s2 XCHG // in_msg_body cs sender_address owner_address 42 | SDEQ // in_msg_body cs _18 43 | 401 THROWIFNOT 44 | SWAP // cs in_msg_body 45 | 32 LDU // cs op in_msg_body 46 | SWAP // cs in_msg_body op 47 | 1 EQINT // cs in_msg_body _25 48 | IF:<{ // cs in_msg_body 49 | LDVARUINT16 // cs _89 _88 50 | DROP // cs amount 51 | SWAP // amount cs 52 | LDREF // amount _31 cs 53 | LDREF // amount _31 _93 _92 54 | DROP // amount state_init body 55 | OVER // amount state_init body state_init 56 | HASHCU // amount state_init body state_init_hash 57 | 0 PUSHINT // amount state_init body state_init_hash _38=0 58 | NEWC // amount state_init body state_init_hash _38=0 _39 59 | 8 STI // amount state_init body state_init_hash _41 60 | 256 STU // amount state_init body _43 61 | ENDC // amount state_init body _44 62 | CTOS // amount state_init body dest_address 63 | 7 PUSHINT // amount state_init body dest_address _51 64 | 4 PUSHINT // amount state_init body dest_address _51 _52=4 65 | 24 PUSHINT // amount state_init body dest_address _51 _52=4 _53=24 66 | NEWC // amount state_init body dest_address _51 _52=4 _53=24 _54 67 | 6 STU // amount state_init body dest_address _51 _52=4 _56 68 | 3 STU // amount state_init body dest_address _51 _58 69 | ROT // amount state_init body _51 _58 dest_address 70 | STSLICER // amount state_init body _51 _59 71 | s0 s4 XCHG2 // _51 state_init body _59 amount 72 | STGRAMS // _51 state_init body _60 73 | s1 s3 XCHG // body state_init _51 _60 74 | 108 STU // body state_init _76 75 | STREF // body _77 76 | STREF // msg 77 | ENDC // _79 78 | 1 PUSHINT // _79 _80=1 79 | SENDRAWMSG 80 | }>ELSE<{ 81 | 2DROP // 82 | }> 83 | }> 84 | recv_external PROC:<{ 85 | // in_msg 86 | DROP // 87 | }> 88 | }END>c 89 | -------------------------------------------------------------------------------- /pyfift/wallet/wallet_v3_r2.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.app import App 2 | from pyfift.core.contract import Contract 3 | from pyfift.core.boc import BoC 4 | from pyfift.wallet.wallet_base import WalletBase 5 | 6 | 7 | class WalletV3R2(Contract, WalletBase): 8 | code = "B5EE9C724101010100710000DEFF0020DD2082014C97BA218201339CBAB19F71B0ED44D0D31FD31F31D70BFFE304E0A4F2608308D71820D31FD31FD31FF82313BBF263ED44D0D31FD31FD3FFD15132BAF2A15144BAF2A204F901541055F910F2A3F8009320D74A96D307D402FB00E8D101A4C8CB1FCB1FCBFFC9ED5410BD6DAD" 9 | initial_dep_msg = ''' 10 | "Asm.fif" include 11 | "TonUtil.fif" include 12 | 13 | 2 boc+>B constant msg 14 | "result boc:{" type msg Bx. "}" type 15 | ''' 16 | state_init = ''' 17 | "Asm.fif" include 18 | "TonUtil.fif" include 19 | B{%pub_key%} 256 B>u@ constant pub_key 20 | 21 | 26 | constant addr_f 27 | addr_f 2 boc+>B constant result 28 | "result boc:{" type result Bx. "}" type 29 | ''' 30 | build_msg = ''' 31 | "Asm.fif" include 32 | "TonUtil.fif" include 33 | B{%message%} B>boc 34 | constant message_bd 35 | 36 | B{%priv_key%} constant priv_key 37 | "%wallet_addr%" $>smca 2drop 2constant my_addr 38 | 39 | constant msg_body_unsigned 40 | 41 | msg_body_unsigned hashB constant msg_hash 42 | msg_hash priv_key ed25519_sign constant msg_signature 43 | 44 | constant msg_body 45 | 46 | '10' 48 | // src addr_none => '00' (from nowhere) 49 | b{1000} s, 50 | my_addr Addr, 51 | // import_fee 52 | 0 Gram, 53 | // Now we go through message body: 54 | // no init field => '0' 55 | // body as ref => '1' 56 | b{01} s, 57 | msg_body ref, 58 | b> 59 | constant external_msg 60 | 61 | external_msg 2 boc+>B constant result 62 | 63 | "result boc:{" type result Bx. "}" type 64 | ''' 65 | 66 | def __init__(self): 67 | self.init_code(code_hex=WalletV3R2.code) 68 | 69 | def pub_key(self) -> int: 70 | o = App.lite_client.get_method(78748, self.address(binary=False)) 71 | return int(o.strip()) 72 | 73 | def seq_no(self) -> int: 74 | o = App.lite_client.get_method(85143, self.address(binary=False)) 75 | return int(o.strip()) 76 | 77 | def init_data(self, pub_key: bytes | BoC = None): 78 | pub_key = pub_key or App.key.pub_key 79 | pub_key = pub_key.hex() if isinstance(pub_key, BoC) else pub_key 80 | outs = App.fift.run(self.state_init, { 81 | "pub_key": pub_key, 82 | }, ["result boc"]) 83 | boc = outs["result boc"] 84 | self.data = BoC(boc) 85 | 86 | def initial_msg(self): 87 | outs = App.fift.run(self.initial_dep_msg, {}, ["result boc"]) 88 | boc = outs["result boc"] 89 | return BoC(boc) 90 | 91 | def build_message(self, sub_wallet: int, valid_until: int, seq_no: int, message: bytes | BoC, message_mode: int = 64): 92 | if isinstance(message, bytes): 93 | message = message.encode('hex') 94 | if isinstance(message, BoC): 95 | message = message.hex() 96 | return self.build_msg, { 97 | "message": message, 98 | "wallet_addr": self.address(binary=False), 99 | "sub_wallet": sub_wallet, 100 | "valid_until": valid_until, 101 | "v_ui": "i" if valid_until == -1 else "u", 102 | "seq_no": seq_no, 103 | "priv_key": App.key.priv_key, 104 | "message_mode": message_mode, 105 | } -------------------------------------------------------------------------------- /pyfift/core/deploy_contract.py: -------------------------------------------------------------------------------- 1 | from pyfift.base.fift import Fift 2 | from pyfift.core.boc import BoC 3 | 4 | 5 | class DeployContract: 6 | build_msg_external = ''' 7 | "Asm.fif" include 8 | "TonUtil.fif" include 9 | B{%message%} B>boc constant msg_body_unsigned 10 | B{%state_init%} B>boc constant init_body 11 | B{%destination%} B>boc constant msg_body_unsigned 15 | 16 | msg_body_unsigned hashB constant msg_hash 17 | msg_hash priv_key ed25519_sign constant msg_signature 18 | 19 | constant msg_body 20 | 21 | '10' 23 | // src addr_none => '00' (from nowhere) 24 | b{1000} s, 25 | dest_addr s, 26 | // import_fee 27 | 0 Gram, 28 | 1 1 u, // has init 29 | 0 1 u, // init ref 30 | init_body 34 | constant ext_msg 35 | 36 | ext_msg 2 boc+>B constant result 37 | 38 | "result boc:{" type result Bx. "}" type 39 | ''' 40 | build_msg_internal = ''' 41 | "Asm.fif" include 42 | "TonUtil.fif" include 43 | B{%message%} B>boc constant msg_body 44 | B{%state_init%} B>boc constant init_body 45 | B{%destination%} B>boc '0' 49 | // disable ihr, allow bounces and is not bounced itself => '010' 50 | // src addr_none => '00' 51 | b{001000} s, 52 | dest_addr s, 53 | %value% Gram, 54 | 0 1 u, // currency dict 55 | 0 Gram, // ihr_fee 56 | 0 Gram, // fwd_fee 57 | 0 64 u, // created_lt 58 | 0 32 u, // created_at 59 | 1 1 u, // has init 60 | 1 1 u, // init ref 61 | init_body ref, 62 | 0 1 u, // body not ref 63 | msg_body 65 | constant internal_msg 66 | 67 | internal_msg 2 boc+>B constant result 68 | 69 | "result boc:{" type result Bx. "}" type 70 | ''' 71 | # int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 72 | # src:MsgAddressInt dest:MsgAddressInt 73 | # value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 74 | # created_lt:uint64 created_at:uint32 = CommonMsgInfo; 75 | 76 | # message$_ {X:Type} info:CommonMsgInfo 77 | # init:(Maybe (Either StateInit ^StateInit)) 78 | # body:(Either X ^X) = Message X; 79 | def __init__(self, message: bytes | BoC, init: bytes | BoC, destination: bytes | BoC, priv_key: bytes | BoC, value: int): 80 | self.message = message or BoC("B5EE9C724101010100020000004CACB9CD") # Empty Bag 81 | self.init = init 82 | self.destination = destination 83 | self.priv_key = priv_key 84 | self.value = value 85 | 86 | if isinstance(self.message, BoC): 87 | self.message = self.message.hex() 88 | if isinstance(self.init, BoC): 89 | self.init = self.init.hex() 90 | if isinstance(self.destination, BoC): 91 | self.destination = self.destination.hex() 92 | if isinstance(self.priv_key, BoC): 93 | self.priv_key = self.priv_key.hex() 94 | 95 | def to_boc(self, external=True): 96 | outs = Fift().run(self.build_msg_internal if not external else self.build_msg_external, { 97 | "message": self.message, 98 | "destination": self.destination, 99 | "state_init": self.init, 100 | "priv_key": self.priv_key, 101 | "value": self.value, 102 | }, ["result boc"]) 103 | boc = outs["result boc"] 104 | return BoC(boc) 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NFT Deployer 2 | This repo provides python scripts to ease NFT deployment on TON (The Open Network). 3 | 4 | ## Prerequisites 5 | 6 | 1. Install `Python3` 7 | 2. [Setup TON Development Environment](https://www.tonspace.co/develop/smart-contracts/environment/installation#build-from-source) 8 | - Make sure to include binaries in your `PATH` before moving ahead! 9 | 10 | ## Getting Started 11 | 12 | 1. Clone the repository 13 | ```sh 14 | git clone https://github.com/anomaly-guard/nft-deployer 15 | cd nft-deployer 16 | ``` 17 | 2. Download necessary configs: 18 | ```sh 19 | mkdir tmp 20 | cd tmp 21 | wget https://ton-blockchain.github.io/global.config.json 22 | wget https://ton-blockchain.github.io/testnet-global.config.json 23 | cd .. 24 | ``` 25 | 26 | ## Setting up the Wallet 27 | 28 | Before moving on you should configure `config.json`. It configures `network`, `your private key`, `lite-client configs`, and `contract codes`. 29 | 30 | > **Warning** 31 | > At least test the flow one time with `testnet` before moving on to `mainnet`. 32 | 33 | To begin your work with deploying, you need to setup your wallet for sending messages and paying for fees. This script currently supports `v3r2` wallets. If you don't have one, upon first execution, keys will be generated and you will be asked to save it. 34 | 35 | 1. Set your private key in `config.json` (If you want key generation, replace the value with `null`) 36 | 2. Run `deploy-wallet.py` 37 | ```sh 38 | python deploy-wallet.py 39 | ``` 40 | 3. According to the account state, the script will notify you with the proper action for you to do. 41 | 4. You can use `@testgiver_ton_bot` to get testnet TONs. 42 | 5. You're done when you see a message like the following message: 43 | ``` 44 | using existing key defined in config ... 45 | This account is already active and deployed ... 46 | Wallet Address: kQC/+HiWP5fgsu9fS7cECGCGON5PZAdKO7fHZ0JyJxV8t6Yj 47 | Balance: 1.46751 TON 48 | ``` 49 | 50 | ## Deploying NFT Collection 51 | 52 | 1. Open `deploy-collection.py` and configure the parameters of `NFTCollection` to create your desired collection: 53 | ```python 54 | # ... 55 | collection = NftCollection() 56 | collection.init_data( 57 | owner=addr, 58 | royalty_factor=5, 59 | royalty_base=100, 60 | next_item_index=0, 61 | collection_content_url='https://raw.githubusercontent.com/ton-blockchain/token-contract/main/nft/web-example/my_collection.json', 62 | common_content_url='https://raw.githubusercontent.com/ton-blockchain/token-contract/main/nft/web-example/', 63 | ) 64 | # ... 65 | ``` 66 | 2. Save and run the deploy script, you will see a message like: 67 | ```sh 68 | ❯ python3 deploy-collection.py 69 | using existing key defined in config ... 70 | preparing to deploy nft collection contract ... 71 | NFT Collection address: kQCtSPsG5JlnAeviQAzc811k2X4aRbHm1OJmzUnpkNNoPgLJ 72 | Deploy message successfully sent to lite-servers! 73 | ``` 74 | 3. Take note of NFT Collection address, we will use it in next section to deploy NFT Items. 75 | 76 | ## Deploy NFT Items 77 | 78 | 1. Edit `deploy-item.py` and replace the values with desired ones: 79 | ```python 80 | nft_collection_addr = "kQCtSPsG5JlnAeviQAzc811k2X4aRbHm1OJmzUnpkNNoPgLJ" 81 | msg_body = DeployNFTMessage( 82 | index=0, 83 | content_url="my_nft.json", 84 | amount=50000000, 85 | owner=addr, 86 | ).to_boc() 87 | ``` 88 | 2. Save and run the deploy script, you will see a message like: 89 | ```sh 90 | ❯ python3 deploy-item.py 91 | using existing key defined in config ... 92 | Message successfully sent to lite-servers! 93 | ``` 94 | 3. Your NFT is successfully deployed! 95 | 96 | ## Notes 97 | 98 | 1. The `0.05 TON` amount in messages are for covering the TON fees. 99 | 2. NFT Item url is not stored completely in NFT Item contract, the common part of url is saved in Collection contract and just the last part is saved in Item contract (To optimize fees). 100 | 3. You can use [tonscan](https://tonscan.org/) or its [testnet version](https://testnet.tonscan.org/) to check transactions status. (Enter address and check its transactions) 101 | 4. You can use [TON NFT Explorer](https://explorer.tonnft.tools/) or its [testnet version](https://testnet.explorer.tonnft.tools/) to check NFT contracts state. 102 | 5. You can customize the codes too, just point the `config.json`'s code path to modified contracts. -------------------------------------------------------------------------------- /token-contract/ft/jetton-minter.fc: -------------------------------------------------------------------------------- 1 | ;; Jettons minter smart contract 2 | 3 | ;; storage scheme 4 | ;; storage#_ total_supply:Coins admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage; 5 | 6 | (int, slice, cell, cell) load_data() inline { 7 | slice ds = get_data().begin_parse(); 8 | return ( 9 | ds~load_coins(), ;; total_supply 10 | ds~load_msg_addr(), ;; admin_address 11 | ds~load_ref(), ;; content 12 | ds~load_ref() ;; jetton_wallet_code 13 | ); 14 | } 15 | 16 | () save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline { 17 | set_data(begin_cell() 18 | .store_coins(total_supply) 19 | .store_slice(admin_address) 20 | .store_ref(content) 21 | .store_ref(jetton_wallet_code) 22 | .end_cell() 23 | ); 24 | } 25 | 26 | () mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure { 27 | cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); 28 | slice to_wallet_address = calculate_jetton_wallet_address(state_init); 29 | var msg = begin_cell() 30 | .store_uint(0x18, 6) 31 | .store_slice(to_wallet_address) 32 | .store_coins(amount) 33 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 34 | .store_ref(state_init) 35 | .store_ref(master_msg); 36 | send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors 37 | } 38 | 39 | () recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { 40 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 41 | return (); 42 | } 43 | slice cs = in_msg_full.begin_parse(); 44 | int flags = cs~load_uint(4); 45 | 46 | if (flags & 1) { ;; ignore all bounced messages 47 | return (); 48 | } 49 | slice sender_address = cs~load_msg_addr(); 50 | 51 | int op = in_msg_body~load_uint(32); 52 | int query_id = in_msg_body~load_uint(64); 53 | 54 | (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); 55 | 56 | if (op == op::mint()) { 57 | throw_unless(73, equal_slices(sender_address, admin_address)); 58 | slice to_address = in_msg_body~load_msg_addr(); 59 | int amount = in_msg_body~load_coins(); 60 | cell master_msg = in_msg_body~load_ref(); 61 | slice master_msg_cs = master_msg.begin_parse(); 62 | master_msg_cs~skip_bits(32 + 64); ;; op + query_id 63 | int jetton_amount = master_msg_cs~load_coins(); 64 | mint_tokens(to_address, jetton_wallet_code, amount, master_msg); 65 | save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code); 66 | return (); 67 | } 68 | 69 | if (op == op::burn_notification()) { 70 | int jetton_amount = in_msg_body~load_coins(); 71 | slice from_address = in_msg_body~load_msg_addr(); 72 | throw_unless(74, 73 | equal_slices(calculate_user_jetton_wallet_address(from_address, my_address(), jetton_wallet_code), sender_address) 74 | ); 75 | save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code); 76 | slice response_address = in_msg_body~load_msg_addr(); 77 | if (response_address.preload_uint(2) != 0) { 78 | var msg = begin_cell() 79 | .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 80 | .store_slice(response_address) 81 | .store_coins(0) 82 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 83 | .store_uint(op::excesses(), 32) 84 | .store_uint(query_id, 64); 85 | send_raw_message(msg.end_cell(), 2 + 64); 86 | } 87 | return (); 88 | } 89 | 90 | if (op == 3) { ;; change admin 91 | throw_unless(73, equal_slices(sender_address, admin_address)); 92 | slice new_admin_address = in_msg_body~load_msg_addr(); 93 | save_data(total_supply, new_admin_address, content, jetton_wallet_code); 94 | return (); 95 | } 96 | 97 | if (op == 4) { ;; change content, delete this for immutable tokens 98 | throw_unless(73, equal_slices(sender_address, admin_address)); 99 | save_data(total_supply, admin_address, in_msg_body~load_ref(), jetton_wallet_code); 100 | return (); 101 | } 102 | 103 | throw(0xffff); 104 | } 105 | 106 | (int, int, slice, cell, cell) get_jetton_data() method_id { 107 | (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); 108 | return (total_supply, -1, admin_address, content, jetton_wallet_code); 109 | } 110 | 111 | slice get_wallet_address(slice owner_address) method_id { 112 | (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); 113 | return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); 114 | } 115 | -------------------------------------------------------------------------------- /fift-libs/Fift.fif: -------------------------------------------------------------------------------- 1 | { 0 word drop 0 'nop } :: // 2 | { char " word 1 { swap { abort } if drop } } ::_ abort" 3 | { { bl word dup "" $= abort"comment extends after end of file" "*/" $= } until 0 'nop } :: /* 4 | // { bl word 1 2 ' (create) } "::" 1 (create) 5 | // { bl word 0 2 ' (create) } :: : 6 | // { bl word 2 2 ' (create) } :: :_ 7 | // { bl word 3 2 ' (create) } :: ::_ 8 | // { bl word 0 (create) } : create 9 | // { bl word (forget) } : forget 10 | { bl word 1 ' (forget) } :: [forget] 11 | { char " word 1 ' type } ::_ ." 12 | { char } word x>B 1 'nop } ::_ B{ 13 | { swap ({) over 2+ -roll swap (compile) (}) } : does 14 | { 1 'nop does create } : constant 15 | { 2 'nop does create } : 2constant 16 | { hole constant } : variable 17 | 10 constant ten 18 | { bl word 1 { find 0= abort"word not found" } } :: (') 19 | { bl word find not abort"-?" 0 swap } :: [compile] 20 | { bl word 1 { 21 | dup find { " -?" $+ abort } ifnot nip execute 22 | } } :: @' 23 | { bl word 1 { swap 1 'nop does swap 0 (create) } 24 | } :: =: 25 | { bl word 1 { -rot 2 'nop does swap 0 (create) } 26 | } :: 2=: 27 | { } : s>c 28 | { s>c hashB } : shash 29 | // to be more efficiently re-implemented in C++ in the future 30 | { dup 0< ' negate if } : abs 31 | { 2dup > ' swap if } : minmax 32 | { minmax drop } : min 33 | { minmax nip } : max 34 | "" constant <# 35 | ' $reverse : #> 36 | { swap 10 /mod char 0 + rot swap hold } : # 37 | { { # over 0<= } until } : #s 38 | { 0< { char - hold } if } : sign 39 | // { dup abs <# #s rot sign #> nip } : (.) 40 | // { (.) type } : ._ 41 | // { ._ space } : . 42 | { dup 10 < { 48 } { 55 } cond + } : Digit 43 | { dup 10 < { 48 } { 87 } cond + } : digit 44 | // x s b -- x' s' 45 | { rot swap /mod Digit rot swap hold } : B# 46 | { rot swap /mod digit rot swap hold } : b# 47 | { 16 B# } : X# 48 | { 16 b# } : x# 49 | // x s b -- 0 s' 50 | { -rot { 2 pick B# over 0<= } until rot drop } : B#s 51 | { -rot { 2 pick b# over 0<= } until rot drop } : b#s 52 | { 16 B#s } : X#s 53 | { 16 b#s } : x#s 54 | variable base 55 | { 10 base ! } : decimal 56 | { 16 base ! } : hex 57 | { 8 base ! } : octal 58 | { 2 base ! } : binary 59 | { base @ B# } : Base# 60 | { base @ b# } : base# 61 | { base @ B#s } : Base#s 62 | { base @ b#s } : base#s 63 | // x w -- s 64 | { over abs <# rot 1- ' X# swap times X#s rot sign #> nip } : (0X.) 65 | { over abs <# rot 1- ' x# swap times x#s rot sign #> nip } : (0x.) 66 | { (0X.) type } : 0X._ 67 | { 0X._ space } : 0X. 68 | { (0x.) type } : 0x._ 69 | { 0x._ space } : 0x. 70 | { bl (-trailing) } : -trailing 71 | { char 0 (-trailing) } : -trailing0 72 | { char " word 1 ' $+ } ::_ +" 73 | { find 0<> dup ' nip if } : (def?) 74 | { bl word 1 ' (def?) } :: def? 75 | { bl word 1 { (def?) not } } :: undef? 76 | { def? ' skip-to-eof if } : skip-ifdef 77 | { bl word dup (def?) { drop skip-to-eof } { 'nop swap 0 (create) } cond } : library 78 | { bl word dup (def?) { 2drop skip-to-eof } { swap 1 'nop does swap 0 (create) } cond } : library-version 79 | { char ) word "$" swap $+ 1 { find 0= abort"undefined parameter" execute } } ::_ $( 80 | // b s -- ? 81 | { sbitrefs rot brembitrefs rot >= -rot <= and } : s-fits? 82 | // b s x -- ? 83 | { swap sbitrefs -rot + rot brembitrefs -rot <= -rot <= and } : s-fits-with? 84 | { 0 swap ! } : 0! 85 | { tuck @ + swap ! } : +! 86 | { tuck @ swap - swap ! } : -! 87 | { 1 swap +! } : 1+! 88 | { -1 swap +! } : 1-! 89 | { null swap ! } : null! 90 | { not 2 pick @ and xor swap ! } : ~! 91 | 0 tuple constant nil 92 | { 1 tuple } : single 93 | { 2 tuple } : pair 94 | { 3 tuple } : triple 95 | { 1 untuple } : unsingle 96 | { 2 untuple } : unpair 97 | { 3 untuple } : untriple 98 | { over tuple? { swap count = } { 2drop false } cond } : tuple-len? 99 | { 0 tuple-len? } : nil? 100 | { 1 tuple-len? } : single? 101 | { 2 tuple-len? } : pair? 102 | { 3 tuple-len? } : triple? 103 | { 0 [] } : first 104 | { 1 [] } : second 105 | { 2 [] } : third 106 | ' pair : cons 107 | ' unpair : uncons 108 | { 0 [] } : car 109 | { 1 [] } : cdr 110 | { cdr car } : cadr 111 | { cdr cdr } : cddr 112 | { cdr cdr car } : caddr 113 | { null ' cons rot times } : list 114 | { -rot pair swap ! } : 2! 115 | { @ unpair } : 2@ 116 | { true (atom) drop } : atom 117 | { bl word atom 1 'nop } ::_ ` 118 | { hole dup 1 { @ execute } does create } : recursive 119 | { 0 { 1+ dup 1 ' $() does over (.) "$" swap $+ 0 (create) } rot times drop } : :$1..n 120 | { 10 hold } : +cr 121 | { 9 hold } : +tab 122 | { "" swap { 0 word 2dup $cmp } { rot swap $+ +cr swap } while 2drop } : scan-until-word 123 | { 0 word -trailing scan-until-word 1 'nop } ::_ $<< 124 | { 0x40 runvmx } : runvmcode 125 | { 0x48 runvmx } : gasrunvmcode 126 | { 0xc8 runvmx } : gas2runvmcode 127 | { 0x43 runvmx } : runvmdict 128 | { 0x4b runvmx } : gasrunvmdict 129 | { 0xcb runvmx } : gas2runvmdict 130 | { 0x45 runvmx } : runvm 131 | { 0x4d runvmx } : gasrunvm 132 | { 0xcd runvmx } : gas2runvm 133 | { 0x55 runvmx } : runvmctx 134 | { 0x5d runvmx } : gasrunvmctx 135 | { 0xdd runvmx } : gas2runvmctx 136 | { 0x75 runvmx } : runvmctxact 137 | { 0x7d runvmx } : gasrunvmctxact 138 | { 0xfd runvmx } : gas2runvmctxact 139 | { 0x35 runvmx } : runvmctxactq 140 | { 0x3d runvmx } : gasrunvmctxactq 141 | -------------------------------------------------------------------------------- /token-contract/ft/jetton-minter-ICO.fc: -------------------------------------------------------------------------------- 1 | ;; Jettons minter smart contract 2 | 3 | ;; storage scheme 4 | ;; storage#_ total_supply:Coins admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage; 5 | 6 | (int, slice, cell, cell) load_data() inline { 7 | slice ds = get_data().begin_parse(); 8 | return ( 9 | ds~load_coins(), ;; total_supply 10 | ds~load_msg_addr(), ;; admin_address 11 | ds~load_ref(), ;; content 12 | ds~load_ref() ;; jetton_wallet_code 13 | ); 14 | } 15 | 16 | () save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline { 17 | set_data(begin_cell() 18 | .store_coins(total_supply) 19 | .store_slice(admin_address) 20 | .store_ref(content) 21 | .store_ref(jetton_wallet_code) 22 | .end_cell() 23 | ); 24 | } 25 | 26 | () mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure { 27 | cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); 28 | slice to_wallet_address = calculate_jetton_wallet_address(state_init); 29 | var msg = begin_cell() 30 | .store_uint(0x18, 6) 31 | .store_slice(to_wallet_address) 32 | .store_coins(amount) 33 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 34 | .store_ref(state_init) 35 | .store_ref(master_msg); 36 | send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors 37 | } 38 | 39 | () recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { 40 | slice cs = in_msg_full.begin_parse(); 41 | int flags = cs~load_uint(4); 42 | 43 | if (flags & 1) { ;; ignore all bounced messages 44 | return (); 45 | } 46 | slice sender_address = cs~load_msg_addr(); 47 | 48 | (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); 49 | 50 | if (in_msg_body.slice_empty?()) { ;; buy jettons for Toncoin 51 | 52 | int amount = 10000000; ;; for mint message 53 | int buy_amount = msg_value - amount; 54 | throw_unless(76, buy_amount > 0); 55 | 56 | int jetton_amount = buy_amount; ;; rate 1 jetton = 1 toncoin; multiply to price here 57 | 58 | var master_msg = begin_cell() 59 | .store_uint(op::internal_transfer(), 32) 60 | .store_uint(0, 64) ;; quert_id 61 | .store_coins(jetton_amount) 62 | .store_slice(my_address()) ;; from_address 63 | .store_slice(sender_address) ;; response_address 64 | .store_coins(0) ;; no forward_amount 65 | .store_uint(0, 1) ;; forward_payload in this slice, not separate cell 66 | .end_cell(); 67 | 68 | mint_tokens(sender_address, jetton_wallet_code, amount, master_msg); 69 | save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code); 70 | return (); 71 | } 72 | 73 | int op = in_msg_body~load_uint(32); 74 | int query_id = in_msg_body~load_uint(64); 75 | 76 | if (op == op::mint()) { 77 | throw_unless(73, equal_slices(sender_address, admin_address)); 78 | slice to_address = in_msg_body~load_msg_addr(); 79 | int amount = in_msg_body~load_coins(); 80 | cell master_msg = in_msg_body~load_ref(); 81 | slice master_msg_cs = master_msg.begin_parse(); 82 | master_msg_cs~skip_bits(32 + 64); ;; op + query_id 83 | int jetton_amount = master_msg_cs~load_coins(); 84 | mint_tokens(to_address, jetton_wallet_code, amount, master_msg); 85 | save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code); 86 | return (); 87 | } 88 | 89 | if (op == op::burn_notification()) { 90 | int jetton_amount = in_msg_body~load_coins(); 91 | slice from_address = in_msg_body~load_msg_addr(); 92 | throw_unless(74, 93 | equal_slices(calculate_user_jetton_wallet_address(from_address, my_address(), jetton_wallet_code), sender_address) 94 | ); 95 | save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code); 96 | slice response_address = in_msg_body~load_msg_addr(); 97 | if (response_address.preload_uint(2) != 0) { 98 | var msg = begin_cell() 99 | .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 100 | .store_slice(response_address) 101 | .store_coins(0) 102 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 103 | .store_uint(op::excesses(), 32) 104 | .store_uint(query_id, 64); 105 | send_raw_message(msg.end_cell(), 2 + 64); 106 | } 107 | return (); 108 | } 109 | 110 | throw(0xffff); 111 | } 112 | 113 | (int, int, slice, cell, cell) get_jetton_data() method_id { 114 | (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); 115 | return (total_supply, -1, admin_address, content, jetton_wallet_code); 116 | } 117 | 118 | slice get_wallet_address(slice owner_address) method_id { 119 | (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); 120 | return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); 121 | } 122 | -------------------------------------------------------------------------------- /token-contract/nft/nft-item.fc: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; TON NFT Item Smart Contract 3 | ;; 4 | 5 | {- 6 | 7 | NOTE that this tokens can be transferred within the same workchain. 8 | 9 | This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions: 10 | 11 | 1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`) 12 | 13 | 2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain) 14 | 15 | -} 16 | 17 | int min_tons_for_storage() asm "50000000 PUSHINT"; ;; 0.05 TON 18 | 19 | ;; 20 | ;; Storage 21 | ;; 22 | ;; uint64 index 23 | ;; MsgAddressInt collection_address 24 | ;; MsgAddressInt owner_address 25 | ;; cell content 26 | ;; 27 | 28 | (int, int, slice, slice, cell) load_data() { 29 | slice ds = get_data().begin_parse(); 30 | var (index, collection_address) = (ds~load_uint(64), ds~load_msg_addr()); 31 | if (ds.slice_bits() > 0) { 32 | return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref()); 33 | } else { 34 | return (0, index, collection_address, null(), null()); ;; nft not initialized yet 35 | } 36 | } 37 | 38 | () store_data(int index, slice collection_address, slice owner_address, cell content) impure { 39 | set_data( 40 | begin_cell() 41 | .store_uint(index, 64) 42 | .store_slice(collection_address) 43 | .store_slice(owner_address) 44 | .store_ref(content) 45 | .end_cell() 46 | ); 47 | } 48 | 49 | () send_msg(slice to_address, int amount, int op, int query_id, builder payload, int send_mode) impure inline { 50 | var msg = begin_cell() 51 | .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 52 | .store_slice(to_address) 53 | .store_coins(amount) 54 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 55 | .store_uint(op, 32) 56 | .store_uint(query_id, 64); 57 | 58 | if (~ builder_null?(payload)) { 59 | msg = msg.store_builder(payload); 60 | } 61 | 62 | send_raw_message(msg.end_cell(), send_mode); 63 | } 64 | 65 | () transfer_ownership(int my_balance, int index, slice collection_address, slice owner_address, cell content, slice sender_address, int query_id, slice in_msg_body, int fwd_fees) impure inline { 66 | throw_unless(401, equal_slices(sender_address, owner_address)); 67 | 68 | slice new_owner_address = in_msg_body~load_msg_addr(); 69 | force_chain(new_owner_address); 70 | slice response_destination = in_msg_body~load_msg_addr(); 71 | in_msg_body~load_int(1); ;; this nft don't use custom_payload 72 | int forward_amount = in_msg_body~load_coins(); 73 | 74 | int rest_amount = my_balance - min_tons_for_storage(); 75 | if (forward_amount) { 76 | rest_amount -= (forward_amount + fwd_fees); 77 | } 78 | int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 00 79 | if (need_response) { 80 | rest_amount -= fwd_fees; 81 | } 82 | 83 | throw_unless(402, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response 84 | 85 | if (forward_amount) { 86 | send_msg(new_owner_address, forward_amount, op::ownership_assigned(), query_id, begin_cell().store_slice(owner_address).store_slice(in_msg_body), 1); ;; paying fees, revert on errors 87 | } 88 | if (need_response) { 89 | force_chain(response_destination); 90 | send_msg(response_destination, rest_amount, op::excesses(), query_id, null(), 1); ;; paying fees, revert on errors 91 | } 92 | 93 | store_data(index, collection_address, new_owner_address, content); 94 | } 95 | 96 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 97 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 98 | return (); 99 | } 100 | 101 | slice cs = in_msg_full.begin_parse(); 102 | int flags = cs~load_uint(4); 103 | 104 | if (flags & 1) { ;; ignore all bounced messages 105 | return (); 106 | } 107 | slice sender_address = cs~load_msg_addr(); 108 | 109 | cs~load_msg_addr(); ;; skip dst 110 | cs~load_coins(); ;; skip value 111 | cs~skip_bits(1); ;; skip extracurrency collection 112 | cs~load_coins(); ;; skip ihr_fee 113 | int fwd_fee = cs~load_coins(); ;; we use message fwd_fee for estimation of forward_payload costs 114 | 115 | 116 | (int init?, int index, slice collection_address, slice owner_address, cell content) = load_data(); 117 | if (~ init?) { 118 | throw_unless(405, equal_slices(collection_address, sender_address)); 119 | store_data(index, collection_address, in_msg_body~load_msg_addr(), in_msg_body~load_ref()); 120 | return (); 121 | } 122 | 123 | int op = in_msg_body~load_uint(32); 124 | int query_id = in_msg_body~load_uint(64); 125 | 126 | if (op == op::transfer()) { 127 | transfer_ownership(my_balance, index, collection_address, owner_address, content, sender_address, query_id, in_msg_body, fwd_fee); 128 | return (); 129 | } 130 | if (op == op::get_static_data()) { 131 | send_msg(sender_address, 0, op::report_static_data(), query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message 132 | return (); 133 | } 134 | throw(0xffff); 135 | } 136 | 137 | ;; 138 | ;; GET Methods 139 | ;; 140 | 141 | (int, int, slice, slice, cell) get_nft_data() method_id { 142 | (int init?, int index, slice collection_address, slice owner_address, cell content) = load_data(); 143 | return (init?, index, collection_address, owner_address, content); 144 | } 145 | -------------------------------------------------------------------------------- /fift-libs/GetOpt.fif: -------------------------------------------------------------------------------- 1 | library GetOpt // Simple command-line options parser 2 | "Lists.fif" include 3 | 4 | // May be used as follows: 5 | // begin-options 6 | // "h" { ."Help Message" 0 halt } short-option 7 | // "v" { parse-int =: verbosity } short-option-arg 8 | // "i" "--interactive" { true =: interactive } short-long-option 9 | // parse-options 10 | 11 | // ( l -- l') computes tail of list l if non-empty; else () 12 | { dup null? ' cdr ifnot } : safe-cdr 13 | // ( l c -- l') deletes first c elements from list l 14 | { ' safe-cdr swap times } : list-delete-first 15 | // ( l n c -- l' ) deletes c elements starting from n-th in list l 16 | recursive list-delete-range { 17 | dup 0<= { 2drop } { 18 | over 0<= { nip list-delete-first } { 19 | swap 1- swap rot uncons 2swap list-delete-range cons 20 | } cond } cond 21 | } swap ! 22 | // ( n c -- ) deletes $n .. $(n+c-1) from the argument list $* 23 | { swap 1- $* @ swap rot list-delete-range $* ! } : $*del.. 24 | // ( s s' -- ? ) checks whether s' is a prefix of s 25 | { tuck $len over $len over >= { $| drop $= } { 2drop drop false } cond 26 | } : $pfx? 27 | // ( s -- ? ) checks whether s is an option (a string beginning with '-') 28 | { dup $len 1 > { "-" $pfx? } { drop false } cond } : is-opt? 29 | // ( s -- ? ) checks whether s is a digit option 30 | { 2 $| drop 1 $| nip $>B 8 B>u@ dup 57 <= swap 48 >= and } : is-digit-opt? 31 | 0 box constant disable-digit-opts 32 | // ( l -- s i or 0 ) finds first string in l beginning with '-' 33 | { 0 { 1+ over null? { 2drop 0 true } { 34 | swap uncons over is-opt? 35 | { disable-digit-opts @ { over is-digit-opt? not } { true } cond } { false } cond 36 | { drop swap true } { nip swap false } cond 37 | } cond } until 38 | } : list-find-opt 39 | // ( -- s i or 0 ) finds first option in cmdline args 40 | { $* @ list-find-opt } : first-opt 41 | ' second : get-opt-flags 42 | ' first : get-opt-exec 43 | // ( s t -- ? ) checks whether short/long option s matches description t 44 | { third $= } : short-option-matches 45 | { dup get-opt-flags 4 and 0= 3 + [] $= 46 | } : long-option-matches 47 | // ( t -- s -1 or 0 ) extracts help message from description 48 | { dup get-opt-flags 4 and 0= 4 + over count over > 49 | { [] true } { 2drop false } cond 50 | } : get-opt-help 51 | // ( s l -- t -1 or 0 ) finds short/long option s in list l 52 | { swap 1 { swap short-option-matches } does assoc-gen 53 | } : lookup-short-option 54 | { swap 1 { swap long-option-matches } does assoc-gen 55 | } : lookup-long-option 56 | // ( s -- s' null or s' s'' ) Splits long option --opt=arg at '=' 57 | { dup "=" $pos 1+ ?dup { tuck $| swap rot 1- $| drop swap } { null } cond 58 | } : split-longopt 59 | // ( l -- f or 0 ) Extracts global option flags from first entry of l 60 | { dup null? { drop 0 } { car get-opt-flags -256 and } cond 61 | } : get-global-option-flags 62 | variable options-list 63 | // ( l -- i or 0 ) 64 | // parses command line arguments according to option description list l 65 | // and returns index i of first incorrect option 66 | { dup options-list ! get-global-option-flags 67 | 256 and disable-digit-opts ! 68 | { first-opt dup 0= { true } { 69 | swap dup "--" $pfx? { // i s 70 | dup $len 2 = { drop dup 1 $*del.. 0 true } { 71 | split-longopt swap options-list @ 72 | lookup-long-option not { drop true } { // i s' t f 73 | dup get-opt-exec swap get-opt-flags 3 and // i s' e f' 74 | 2 pick null? { dup 1 = } { dup 0= negate } cond // i s' e f' f'' 75 | dup 1 = { 2drop 2drop true } { 76 | { drop nip over 1+ $() swap execute 2 $*del.. false } { 77 | ' nip ifnot execute 1 $*del.. false 78 | } cond } cond } cond } cond } { // i s 79 | 1 $| nip { 80 | dup $len 0= { drop 1 $*del.. false true } { 81 | 1 $| swap options-list @ // i s' s l 82 | lookup-short-option not { drop true true } { // i s' t 83 | dup get-opt-exec swap get-opt-flags 3 and // i s' e f' 84 | ?dup 0= { execute false } { 85 | 2 pick $len { drop execute "" false } { 86 | 2 = { nip null swap execute "" false } { // i e 87 | nip over 1+ $() swap execute 2 $*del.. false true 88 | } cond } cond } cond } cond } cond } until 89 | } cond 90 | } cond } until 91 | } : getopt 92 | // ( t -- ) Displays help message for one option 93 | { dup get-opt-flags dup 4 and 2 pick third swap { 94 | ."-" type ."/" over 3 [] type } { 95 | dup $len { dup "--" $pfx? { ."-" } ifnot type } { 96 | drop ."usage: " $0 type 97 | } cond } cond 98 | dup 3 and ?dup { 99 | 2 = { ."[=]" } { ."=" } cond 100 | } if 101 | 8 and { 9 emit } ifnot 102 | get-opt-help { type } { ."No help available" } cond cr 103 | } : show-opt-help 104 | // ( -- ) Displays options help message according to options-list 105 | { options-list @ { dup null? not } { 106 | uncons swap show-opt-help 107 | } while drop 108 | } : show-options-help 109 | // ( l -- ) Parses options and throws an error on failure 110 | { getopt ?dup { 111 | $() "cannot parse command line options near `" swap $+ +"`" 112 | show-options-help abort } if 113 | } : run-getopt 114 | anon constant opt-list-marker 115 | ' opt-list-marker : begin-options 116 | { opt-list-marker list-until-marker } : end-options 117 | { end-options run-getopt } : parse-options 118 | // ( s e -- o ) Creates short/long option s with execution token e 119 | { 0 rot triple } dup : short-option : long-option 120 | // ( s s' e -- o ) Creates a combined short option s and long option s' with execution token e 121 | { 4 2swap 4 tuple } : short-long-option 122 | { 1 rot triple } dup : short-option-arg : long-option-arg 123 | { 2 rot triple } dup : short-option-?arg : long-option-?arg 124 | { 5 2swap 4 tuple } : short-long-option-arg 125 | { 6 2swap 4 tuple } : short-long-option-?arg 126 | // ( o s -- s' ) Adds help message to option 127 | ' , : option-help 128 | // ( s f -- o ) Creates a generic help message 129 | { swap 'nop rot "" 3 roll 4 tuple } : generic-help-setopt 130 | { 0 generic-help-setopt } : generic-help 131 | 256 constant disable-digit-options 132 | -------------------------------------------------------------------------------- /token-contract/nft/web-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NFT Marketplace 6 | 7 | 8 | 9 | 10 | TON Wallet Chrome Extension 1.1.34+ required. 11 | 12 | 13 | 14 | 15 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /token-contract/nft/nft-sale.fc: -------------------------------------------------------------------------------- 1 | ;; NFT sale smart contract 2 | 3 | int min_gas_amount() asm "1000000000 PUSHINT"; ;; 1 TON 4 | 5 | (slice, slice, slice, int, cell) load_data() inline { 6 | var ds = get_data().begin_parse(); 7 | return 8 | (ds~load_msg_addr(), ;; marketplace_address 9 | ds~load_msg_addr(), ;; nft_address 10 | ds~load_msg_addr(), ;; nft_owner_address 11 | ds~load_coins(), ;; full_price 12 | ds~load_ref() ;; fees_cell 13 | ); 14 | } 15 | 16 | (int, slice, int) load_fees(cell fees_cell) inline { 17 | var ds = fees_cell.begin_parse(); 18 | return 19 | (ds~load_coins(), ;; marketplace_fee, 20 | ds~load_msg_addr(), ;; royalty_address 21 | ds~load_coins() ;; royalty_amount 22 | ); 23 | } 24 | 25 | 26 | () save_data(slice marketplace_address, slice nft_address, slice nft_owner_address, int full_price, cell fees_cell) impure inline { 27 | set_data(begin_cell() 28 | .store_slice(marketplace_address) 29 | .store_slice(nft_address) 30 | .store_slice(nft_owner_address) 31 | .store_coins(full_price) 32 | .store_ref(fees_cell) 33 | .end_cell()); 34 | } 35 | 36 | () buy(int my_balance, slice marketplace_address, slice nft_address, slice nft_owner_address, int full_price, cell fees_cell, int msg_value, slice sender_address, int query_id) impure { 37 | throw_unless(450, msg_value >= full_price + min_gas_amount()); 38 | 39 | var (marketplace_fee, royalty_address, royalty_amount) = load_fees(fees_cell); 40 | 41 | var owner_msg = begin_cell() 42 | .store_uint(0x10, 6) ;; nobounce 43 | .store_slice(nft_owner_address) 44 | .store_coins(full_price - marketplace_fee - royalty_amount + (my_balance - msg_value)) 45 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1); 46 | 47 | send_raw_message(owner_msg.end_cell(), 1); 48 | 49 | 50 | var royalty_msg = begin_cell() 51 | .store_uint(0x10, 6) ;; nobounce 52 | .store_slice(royalty_address) 53 | .store_coins(royalty_amount) 54 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1); 55 | 56 | send_raw_message(royalty_msg.end_cell(), 1); 57 | 58 | 59 | var marketplace_msg = begin_cell() 60 | .store_uint(0x10, 6) ;; nobounce 61 | .store_slice(marketplace_address) 62 | .store_coins(marketplace_fee) 63 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1); 64 | 65 | send_raw_message(marketplace_msg.end_cell(), 1); 66 | 67 | var nft_msg = begin_cell() 68 | .store_uint(0x18, 6) 69 | .store_slice(nft_address) 70 | .store_coins(0) 71 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 72 | .store_uint(op::transfer(), 32) 73 | .store_uint(query_id, 64) 74 | .store_slice(sender_address) ;; new_owner_address 75 | .store_slice(sender_address) ;; response_address 76 | .store_int(0, 1) ;; empty custom_payload 77 | .store_coins(0) ;; forward amount to new_owner_address 78 | .store_int(0, 1); ;; empty forward_payload 79 | 80 | 81 | send_raw_message(nft_msg.end_cell(), 128 + 32); 82 | } 83 | 84 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 85 | slice cs = in_msg_full.begin_parse(); 86 | int flags = cs~load_uint(4); 87 | 88 | if (flags & 1) { ;; ignore all bounced messages 89 | return (); 90 | } 91 | 92 | slice sender_address = cs~load_msg_addr(); 93 | 94 | var (marketplace_address, nft_address, nft_owner_address, full_price, fees_cell) = load_data(); 95 | 96 | var is_initialized = nft_owner_address.slice_bits() > 2; ;; not initialized if null address 97 | 98 | if (~ is_initialized) { 99 | 100 | if (equal_slices(sender_address, marketplace_address)) { 101 | return (); ;; just accept coins on deploy 102 | } 103 | 104 | throw_unless(500, equal_slices(sender_address, nft_address)); 105 | int op = in_msg_body~load_uint(32); 106 | throw_unless(501, op == op::ownership_assigned()); 107 | int query_id = in_msg_body~load_uint(64); 108 | slice prev_owner_address = in_msg_body~load_msg_addr(); 109 | 110 | save_data(marketplace_address, nft_address, prev_owner_address, full_price, fees_cell); 111 | 112 | return (); 113 | 114 | } 115 | 116 | if (in_msg_body.slice_empty?()) { 117 | buy(my_balance, marketplace_address, nft_address, nft_owner_address, full_price, fees_cell, msg_value, sender_address, 0); 118 | return (); 119 | } 120 | 121 | int op = in_msg_body~load_uint(32); 122 | int query_id = in_msg_body~load_uint(64); 123 | 124 | if (op == 1) { ;; just accept coins 125 | return (); 126 | } 127 | 128 | if (op == 2) { ;; buy 129 | 130 | buy(my_balance, marketplace_address, nft_address, nft_owner_address, full_price, fees_cell, msg_value, sender_address, query_id); 131 | 132 | return (); 133 | 134 | } 135 | 136 | if (op == 3) { ;; cancel sale 137 | throw_unless(457, msg_value >= min_gas_amount()); 138 | throw_unless(458, equal_slices(sender_address, nft_owner_address) | equal_slices(sender_address, marketplace_address)); 139 | 140 | var msg = begin_cell() 141 | .store_uint(0x10, 6) ;; nobounce 142 | .store_slice(nft_address) 143 | .store_coins(0) 144 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 145 | .store_uint(op::transfer(), 32) 146 | .store_uint(query_id, 64) 147 | .store_slice(nft_owner_address) ;; new_owner_address 148 | .store_slice(nft_owner_address) ;; response_address; 149 | .store_int(0, 1) ;; empty custom_payload 150 | .store_coins(0) ;; forward amount to new_owner_address 151 | .store_int(0, 1); ;; empty forward_payload 152 | 153 | send_raw_message(msg.end_cell(), 128 + 32); 154 | 155 | return (); 156 | } 157 | 158 | throw(0xffff); 159 | 160 | } 161 | 162 | () recv_external(slice in_msg) impure { 163 | } 164 | 165 | (slice, slice, slice, int, int, slice, int) get_sale_data() method_id { 166 | var (marketplace_address, nft_address, nft_owner_address, full_price, fees_cell) = load_data(); 167 | var (marketplace_fee, royalty_address, royalty_amount) = load_fees(fees_cell); 168 | return (marketplace_address, nft_address, nft_owner_address, full_price, marketplace_fee, royalty_address, royalty_amount); 169 | } 170 | -------------------------------------------------------------------------------- /token-contract/nft/nft-collection.fc: -------------------------------------------------------------------------------- 1 | ;; NFT collection smart contract 2 | 3 | ;; storage scheme 4 | ;; default#_ royalty_factor:uint16 royalty_base:uint16 royalty_address:MsgAddress = RoyaltyParams; 5 | ;; storage#_ owner_address:MsgAddress next_item_index:uint64 6 | ;; ^[collection_content:^Cell common_content:^Cell] 7 | ;; nft_item_code:^Cell 8 | ;; royalty_params:^RoyaltyParams 9 | ;; = Storage; 10 | 11 | (slice, int, cell, cell, cell) load_data() inline { 12 | var ds = get_data().begin_parse(); 13 | return 14 | (ds~load_msg_addr(), ;; owner_address 15 | ds~load_uint(64), ;; next_item_index 16 | ds~load_ref(), ;; content 17 | ds~load_ref(), ;; nft_item_code 18 | ds~load_ref() ;; royalty_params 19 | ); 20 | } 21 | 22 | () save_data(slice owner_address, int next_item_index, cell content, cell nft_item_code, cell royalty_params) impure inline { 23 | set_data(begin_cell() 24 | .store_slice(owner_address) 25 | .store_uint(next_item_index, 64) 26 | .store_ref(content) 27 | .store_ref(nft_item_code) 28 | .store_ref(royalty_params) 29 | .end_cell()); 30 | } 31 | 32 | cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { 33 | cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell(); 34 | return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); 35 | } 36 | 37 | slice calculate_nft_item_address(int wc, cell state_init) { 38 | return begin_cell().store_uint(4, 3) 39 | .store_int(wc, 8) 40 | .store_uint(cell_hash(state_init), 256) 41 | .end_cell() 42 | .begin_parse(); 43 | } 44 | 45 | () deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure { 46 | cell state_init = calculate_nft_item_state_init(item_index, nft_item_code); 47 | slice nft_address = calculate_nft_item_address(workchain(), state_init); 48 | var msg = begin_cell() 49 | .store_uint(0x18, 6) 50 | .store_slice(nft_address) 51 | .store_coins(amount) 52 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 53 | .store_ref(state_init) 54 | .store_ref(nft_content); 55 | send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors 56 | } 57 | 58 | () send_royalty_params(slice to_address, int query_id, slice data) impure inline { 59 | var msg = begin_cell() 60 | .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 61 | .store_slice(to_address) 62 | .store_coins(0) 63 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 64 | .store_uint(op::report_royalty_params(), 32) 65 | .store_uint(query_id, 64) 66 | .store_slice(data); 67 | send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message 68 | } 69 | 70 | () recv_internal(cell in_msg_full, slice in_msg_body) impure { 71 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 72 | return (); 73 | } 74 | slice cs = in_msg_full.begin_parse(); 75 | int flags = cs~load_uint(4); 76 | 77 | if (flags & 1) { ;; ignore all bounced messages 78 | return (); 79 | } 80 | slice sender_address = cs~load_msg_addr(); 81 | 82 | int op = in_msg_body~load_uint(32); 83 | int query_id = in_msg_body~load_uint(64); 84 | 85 | var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data(); 86 | 87 | if (op == op::get_royalty_params()) { 88 | send_royalty_params(sender_address, query_id, royalty_params.begin_parse()); 89 | return (); 90 | } 91 | 92 | throw_unless(401, equal_slices(sender_address, owner_address)); 93 | 94 | 95 | if (op == 1) { ;; deploy new nft 96 | int item_index = in_msg_body~load_uint(64); 97 | throw_unless(402, item_index <= next_item_index); 98 | var is_last = item_index == next_item_index; 99 | deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref()); 100 | if (is_last) { 101 | next_item_index += 1; 102 | save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); 103 | } 104 | return (); 105 | } 106 | if (op == 2) { ;; batch deploy of new nfts 107 | int counter = 0; 108 | cell deploy_list = in_msg_body~load_ref(); 109 | do { 110 | var (item_index, item, f?) = deploy_list~udict::delete_get_min(64); 111 | if (f?) { 112 | counter += 1; 113 | if (counter >= 250) { ;; Limit due to limits of action list size 114 | throw(399); 115 | } 116 | 117 | throw_unless(403 + counter, item_index <= next_item_index); 118 | deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref()); 119 | if (item_index == next_item_index) { 120 | next_item_index += 1; 121 | } 122 | } 123 | } until ( ~ f?); 124 | save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); 125 | return (); 126 | } 127 | if (op == 3) { ;; change owner 128 | slice new_owner = in_msg_body~load_msg_addr(); 129 | save_data(new_owner, next_item_index, content, nft_item_code, royalty_params); 130 | return (); 131 | } 132 | throw(0xffff); 133 | } 134 | 135 | ;; Get methods 136 | 137 | (int, cell, slice) get_collection_data() method_id { 138 | var (owner_address, next_item_index, content, _, _) = load_data(); 139 | slice cs = content.begin_parse(); 140 | return (next_item_index, cs~load_ref(), owner_address); 141 | } 142 | 143 | slice get_nft_address_by_index(int index) method_id { 144 | var (_, _, _, nft_item_code, _) = load_data(); 145 | cell state_init = calculate_nft_item_state_init(index, nft_item_code); 146 | return calculate_nft_item_address(workchain(), state_init); 147 | } 148 | 149 | (int, int, slice) royalty_params() method_id { 150 | var (_, _, _, _, royalty) = load_data(); 151 | slice rs = royalty.begin_parse(); 152 | return (rs~load_uint(16), rs~load_uint(16), rs~load_msg_addr()); 153 | } 154 | 155 | cell get_nft_content(int index, cell individual_nft_content) method_id { 156 | var (_, _, content, _, _) = load_data(); 157 | slice cs = content.begin_parse(); 158 | cs~load_ref(); 159 | slice common_content = cs~load_ref().begin_parse(); 160 | return (begin_cell() 161 | .store_uint(1, 8) ;; offchain tag 162 | .store_slice(common_content) 163 | .store_ref(individual_nft_content) 164 | .end_cell()); 165 | } 166 | -------------------------------------------------------------------------------- /token-contract/nft/nft-collection-editable.fc: -------------------------------------------------------------------------------- 1 | ;; NFT collection smart contract 2 | 3 | ;; storage scheme 4 | ;; default#_ royalty_factor:uint16 royalty_base:uint16 royalty_address:MsgAddress = RoyaltyParams; 5 | ;; storage#_ owner_address:MsgAddress next_item_index:uint64 6 | ;; ^[collection_content:^Cell common_content:^Cell] 7 | ;; nft_item_code:^Cell 8 | ;; royalty_params:^RoyaltyParams 9 | ;; = Storage; 10 | 11 | (slice, int, cell, cell, cell) load_data() inline { 12 | var ds = get_data().begin_parse(); 13 | return 14 | (ds~load_msg_addr(), ;; owner_address 15 | ds~load_uint(64), ;; next_item_index 16 | ds~load_ref(), ;; content 17 | ds~load_ref(), ;; nft_item_code 18 | ds~load_ref() ;; royalty_params 19 | ); 20 | } 21 | 22 | () save_data(slice owner_address, int next_item_index, cell content, cell nft_item_code, cell royalty_params) impure inline { 23 | set_data(begin_cell() 24 | .store_slice(owner_address) 25 | .store_uint(next_item_index, 64) 26 | .store_ref(content) 27 | .store_ref(nft_item_code) 28 | .store_ref(royalty_params) 29 | .end_cell()); 30 | } 31 | 32 | cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { 33 | cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell(); 34 | return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell(); 35 | } 36 | 37 | slice calculate_nft_item_address(int wc, cell state_init) { 38 | return begin_cell().store_uint(4, 3) 39 | .store_int(wc, 8) 40 | .store_uint(cell_hash(state_init), 256) 41 | .end_cell() 42 | .begin_parse(); 43 | } 44 | 45 | () deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure { 46 | cell state_init = calculate_nft_item_state_init(item_index, nft_item_code); 47 | slice nft_address = calculate_nft_item_address(workchain(), state_init); 48 | var msg = begin_cell() 49 | .store_uint(0x18, 6) 50 | .store_slice(nft_address) 51 | .store_coins(amount) 52 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 53 | .store_ref(state_init) 54 | .store_ref(nft_content); 55 | send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors 56 | } 57 | 58 | () send_royalty_params(slice to_address, int query_id, slice data) impure inline { 59 | var msg = begin_cell() 60 | .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 61 | .store_slice(to_address) 62 | .store_coins(0) 63 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 64 | .store_uint(op::report_royalty_params(), 32) 65 | .store_uint(query_id, 64) 66 | .store_slice(data); 67 | send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message 68 | } 69 | 70 | () recv_internal(cell in_msg_full, slice in_msg_body) impure { 71 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 72 | return (); 73 | } 74 | slice cs = in_msg_full.begin_parse(); 75 | int flags = cs~load_uint(4); 76 | 77 | if (flags & 1) { ;; ignore all bounced messages 78 | return (); 79 | } 80 | slice sender_address = cs~load_msg_addr(); 81 | 82 | int op = in_msg_body~load_uint(32); 83 | int query_id = in_msg_body~load_uint(64); 84 | 85 | var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data(); 86 | 87 | if (op == op::get_royalty_params()) { 88 | send_royalty_params(sender_address, query_id, royalty_params.begin_parse()); 89 | return (); 90 | } 91 | 92 | throw_unless(401, equal_slices(sender_address, owner_address)); 93 | 94 | 95 | if (op == 1) { ;; deploy new nft 96 | int item_index = in_msg_body~load_uint(64); 97 | throw_unless(402, item_index <= next_item_index); 98 | var is_last = item_index == next_item_index; 99 | deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref()); 100 | if (is_last) { 101 | next_item_index += 1; 102 | save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); 103 | } 104 | return (); 105 | } 106 | if (op == 2) { ;; batch deploy of new nfts 107 | int counter = 0; 108 | cell deploy_list = in_msg_body~load_ref(); 109 | do { 110 | var (item_index, item, f?) = deploy_list~udict::delete_get_min(64); 111 | if (f?) { 112 | counter += 1; 113 | if (counter >= 250) { ;; Limit due to limits of action list size 114 | throw(399); 115 | } 116 | 117 | throw_unless(403 + counter, item_index <= next_item_index); 118 | deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref()); 119 | if (item_index == next_item_index) { 120 | next_item_index += 1; 121 | } 122 | } 123 | } until ( ~ f?); 124 | save_data(owner_address, next_item_index, content, nft_item_code, royalty_params); 125 | return (); 126 | } 127 | if (op == 3) { ;; change owner 128 | slice new_owner = in_msg_body~load_msg_addr(); 129 | save_data(new_owner, next_item_index, content, nft_item_code, royalty_params); 130 | return (); 131 | } 132 | if (op == 4) { ;; change content 133 | save_data(owner_address, next_item_index, in_msg_body~load_ref(), nft_item_code, in_msg_body~load_ref()); 134 | return (); 135 | } 136 | throw(0xffff); 137 | } 138 | 139 | ;; Get methods 140 | 141 | (int, cell, slice) get_collection_data() method_id { 142 | var (owner_address, next_item_index, content, _, _) = load_data(); 143 | slice cs = content.begin_parse(); 144 | return (next_item_index, cs~load_ref(), owner_address); 145 | } 146 | 147 | slice get_nft_address_by_index(int index) method_id { 148 | var (_, _, _, nft_item_code, _) = load_data(); 149 | cell state_init = calculate_nft_item_state_init(index, nft_item_code); 150 | return calculate_nft_item_address(workchain(), state_init); 151 | } 152 | 153 | (int, int, slice) royalty_params() method_id { 154 | var (_, _, _, _, royalty) = load_data(); 155 | slice rs = royalty.begin_parse(); 156 | return (rs~load_uint(16), rs~load_uint(16), rs~load_msg_addr()); 157 | } 158 | 159 | cell get_nft_content(int index, cell individual_nft_content) method_id { 160 | var (_, _, content, _, _) = load_data(); 161 | slice cs = content.begin_parse(); 162 | cs~load_ref(); 163 | slice common_content = cs~load_ref().begin_parse(); 164 | return (begin_cell() 165 | .store_uint(1, 8) ;; offchain tag 166 | .store_slice(common_content) 167 | .store_ref(individual_nft_content) 168 | .end_cell()); 169 | } 170 | -------------------------------------------------------------------------------- /token-contract/nft/nft-item-editable-DRAFT.fc: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; TON NFT Item Smart Contract 3 | ;; 4 | 5 | {- 6 | 7 | NOTE that this tokens can be transferred within the same workchain. 8 | 9 | This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions: 10 | 11 | 1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`) 12 | 13 | 2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain) 14 | 15 | -} 16 | 17 | int min_tons_for_storage() asm "50000000 PUSHINT"; ;; 0.05 TON 18 | 19 | ;; 20 | ;; Storage 21 | ;; 22 | ;; uint64 index 23 | ;; MsgAddressInt collection_address 24 | ;; MsgAddressInt owner_address 25 | ;; cell content 26 | ;; MsgAddressInt editor_address 27 | ;; 28 | 29 | (int, int, slice, slice, cell, slice) load_data() { 30 | slice ds = get_data().begin_parse(); 31 | var (index, collection_address) = (ds~load_uint(64), ds~load_msg_addr()); 32 | if (ds.slice_bits() > 0) { 33 | return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref(), ds~load_msg_addr()); 34 | } else { 35 | return (0, index, collection_address, null(), null(), null()); ;; nft not initialized yet 36 | } 37 | } 38 | 39 | () store_data(int index, slice collection_address, slice owner_address, cell content, slice editor_address) impure { 40 | set_data( 41 | begin_cell() 42 | .store_uint(index, 64) 43 | .store_slice(collection_address) 44 | .store_slice(owner_address) 45 | .store_ref(content) 46 | .store_slice(editor_address) 47 | .end_cell() 48 | ); 49 | } 50 | 51 | () send_msg(slice to_address, int amount, int op, int query_id, builder payload, int send_mode) impure inline { 52 | var msg = begin_cell() 53 | .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 54 | .store_slice(to_address) 55 | .store_coins(amount) 56 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 57 | .store_uint(op, 32) 58 | .store_uint(query_id, 64); 59 | 60 | if (~ builder_null?(payload)) { 61 | msg = msg.store_builder(payload); 62 | } 63 | 64 | send_raw_message(msg.end_cell(), send_mode); 65 | } 66 | 67 | () transfer_ownership(int my_balance, int index, slice collection_address, slice owner_address, cell content, slice editor_address, slice sender_address, int query_id, slice in_msg_body, int fwd_fees) impure inline { 68 | throw_unless(401, equal_slices(sender_address, owner_address)); 69 | 70 | slice new_owner_address = in_msg_body~load_msg_addr(); 71 | force_chain(new_owner_address); 72 | slice response_destination = in_msg_body~load_msg_addr(); 73 | in_msg_body~load_int(1); ;; this nft don't use custom_payload 74 | int forward_amount = in_msg_body~load_coins(); 75 | 76 | int rest_amount = my_balance - min_tons_for_storage(); 77 | if (forward_amount) { 78 | rest_amount -= (forward_amount + fwd_fees); 79 | } 80 | int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 00 81 | if (need_response) { 82 | rest_amount -= fwd_fees; 83 | } 84 | 85 | throw_unless(402, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response 86 | 87 | if (forward_amount) { 88 | send_msg(new_owner_address, forward_amount, op::ownership_assigned(), query_id, begin_cell().store_slice(owner_address).store_slice(in_msg_body), 1); ;; paying fees, revert on errors 89 | } 90 | if (need_response) { 91 | force_chain(response_destination); 92 | send_msg(response_destination, rest_amount, op::excesses(), query_id, null(), 1); ;; paying fees, revert on errors 93 | } 94 | 95 | store_data(index, collection_address, new_owner_address, content, editor_address); 96 | } 97 | 98 | () transfer_editorship(int my_balance, int index, slice collection_address, slice owner_address, cell content, slice editor_address, slice sender_address, int query_id, slice in_msg_body, int fwd_fees) impure inline { 99 | throw_unless(401, equal_slices(sender_address, editor_address)); 100 | 101 | slice new_editor_address = in_msg_body~load_msg_addr(); 102 | force_chain(new_editor_address); 103 | slice response_destination = in_msg_body~load_msg_addr(); 104 | in_msg_body~load_int(1); ;; this nft don't use custom_payload 105 | int forward_amount = in_msg_body~load_coins(); 106 | 107 | int rest_amount = my_balance - min_tons_for_storage(); 108 | if (forward_amount) { 109 | rest_amount -= (forward_amount + fwd_fees); 110 | } 111 | int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 00 112 | if (need_response) { 113 | rest_amount -= fwd_fees; 114 | } 115 | 116 | throw_unless(402, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response 117 | 118 | if (forward_amount) { 119 | send_msg(new_editor_address, forward_amount, op::editorship_assigned(), query_id, begin_cell().store_slice(editor_address).store_slice(in_msg_body), 1); ;; paying fees, revert on errors 120 | } 121 | if (need_response) { 122 | force_chain(response_destination); 123 | send_msg(response_destination, rest_amount, op::excesses(), query_id, null(), 1); ;; paying fees, revert on errors 124 | } 125 | 126 | store_data(index, collection_address, owner_address, content, new_editor_address); 127 | } 128 | 129 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 130 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 131 | return (); 132 | } 133 | 134 | slice cs = in_msg_full.begin_parse(); 135 | int flags = cs~load_uint(4); 136 | 137 | if (flags & 1) { ;; ignore all bounced messages 138 | return (); 139 | } 140 | slice sender_address = cs~load_msg_addr(); 141 | 142 | cs~load_msg_addr(); ;; skip dst 143 | cs~load_coins(); ;; skip value 144 | cs~skip_bits(1); ;; skip extracurrency collection 145 | cs~load_coins(); ;; skip ihr_fee 146 | int fwd_fee = cs~load_coins(); ;; we use message fwd_fee for estimation of forward_payload costs 147 | 148 | (int init?, int index, slice collection_address, slice owner_address, cell content, slice editor_address) = load_data(); 149 | if (~ init?) { 150 | throw_unless(405, equal_slices(collection_address, sender_address)); 151 | store_data(index, collection_address, in_msg_body~load_msg_addr(), in_msg_body~load_ref(), in_msg_body~load_msg_addr()); 152 | return (); 153 | } 154 | 155 | int op = in_msg_body~load_uint(32); 156 | int query_id = in_msg_body~load_uint(64); 157 | 158 | if (op == op::transfer()) { 159 | transfer_ownership(my_balance, index, collection_address, owner_address, content, editor_address, sender_address, query_id, in_msg_body, fwd_fee); 160 | return (); 161 | } 162 | if (op == op::get_static_data()) { 163 | send_msg(sender_address, 0, op::report_static_data(), query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message 164 | return (); 165 | } 166 | if (op == op::transfer_editorship()) { 167 | transfer_editorship(my_balance, index, collection_address, owner_address, content, editor_address, sender_address, query_id, in_msg_body, fwd_fee); 168 | return (); 169 | } 170 | if (op == op::edit_content()) { 171 | throw_unless(410, equal_slices(sender_address, editor_address)); 172 | store_data(index, collection_address, owner_address, in_msg_body~load_ref(), editor_address); 173 | return (); 174 | } 175 | throw(0xffff); 176 | } 177 | 178 | ;; 179 | ;; GET Methods 180 | ;; 181 | 182 | (int, int, slice, slice, cell) get_nft_data() method_id { 183 | (int init?, int index, slice collection_address, slice owner_address, cell content, _) = load_data(); 184 | return (init?, index, collection_address, owner_address, content); 185 | } 186 | 187 | slice get_editor() method_id { 188 | (_, _, _, _, _, slice editor_address) = load_data(); 189 | return editor_address; 190 | } 191 | -------------------------------------------------------------------------------- /fift-libs/Lists.fif: -------------------------------------------------------------------------------- 1 | library Lists // List utilities 2 | // 3 | { hole dup 1 { @ execute } does create } : recursive 4 | // x x' -- ? recursively compares two S-expressions 5 | recursive equal? { 6 | dup tuple? { 7 | over tuple? { 8 | over count over count over = { // t t' l ? 9 | 0 { dup 0>= { 2dup [] 3 pick 2 pick [] equal? { 1+ } { drop -1 } cond 10 | } if } rot times 11 | nip nip 0>= 12 | } { drop 2drop false } cond 13 | } { 2drop false } cond 14 | } { eqv? } cond 15 | } swap ! 16 | // (a1 .. an) -- (an .. a1) 17 | { null swap { dup null? not } { uncons swap rot cons swap } while drop } : list-reverse 18 | // (a1 .. an) -- an Computes last element of non-empty list l 19 | { { uncons dup null? { drop true } { nip false } cond } until } : list-last 20 | // l l' -- l++l' Concatenates two lists 21 | recursive list+ { 22 | over null? { nip } { swap uncons rot list+ cons } cond 23 | } swap ! 24 | // l l' -- l'' -1 or 0, where l = l' ++ l'' 25 | // Removes prefix from list 26 | { { dup null? { drop true true } { 27 | swap dup null? { 2drop false true } { // l' l 28 | uncons swap rot uncons -rot equal? { false } { 29 | 2drop false true 30 | } cond } cond } cond } until 31 | } : list- 32 | // (a1 .. an) -- a1 .. an n Explodes a list 33 | { 0 { over null? not } { swap uncons rot 1+ } while nip } : explode-list 34 | // (a1 .. an) x -- a1 .. an n x Explodes a list under the topmost element 35 | { swap explode-list dup 1+ roll } : explode-list-1 36 | // l -- t Transforms a list into a tuple with the same elements 37 | { explode-list tuple } : list>tuple 38 | // a1 ... an n x -- (a1 .. an) x 39 | { null swap rot { -rot cons swap } swap times } : mklist-1 40 | // (s1 ... sn) -- s1+...+sn Concatenates a list of strings 41 | { "" { over null? not } { swap uncons -rot $+ } while nip 42 | } : concat-string-list 43 | // (x1 ... xn) -- x1+...+xn Sums a list of integers 44 | { 0 { over null? not } { swap uncons -rot + } while nip 45 | } : sum-list 46 | // (a1 ... an) a e -- e(...e(e(a,a1),a2),...),an) 47 | { -rot { over null? not } { swap uncons -rot 3 pick execute } while nip nip 48 | } : foldl 49 | // (a1 ... an) e -- e(...e(e(a1,a2),a3),...),an) 50 | { swap uncons swap rot foldl } : foldl-ne 51 | // (a1 ... an) a e -- e(a1,e(a2,...,e(an,a)...)) 52 | recursive foldr { 53 | rot dup null? { 2drop } { 54 | uncons -rot 2swap swap 3 pick foldr rot execute 55 | } cond 56 | } swap ! 57 | // (a1 ... an) e -- e(a1,e(a2,...,e(a[n-1],an)...)) 58 | recursive foldr-ne { 59 | over cdr null? { drop car } { 60 | swap uncons 2 pick foldr-ne rot execute 61 | } cond 62 | } swap ! 63 | // (l1 ... ln) -- l1++...++ln Concatenates a list of lists 64 | { dup null? { ' list+ foldr-ne } ifnot } : concat-list-lists 65 | // (a1 .. an . t) n -- t Computes the n-th tail of a list 66 | { ' cdr swap times } : list-tail 67 | // (a0 .. an ..) n -- an Computes the n-th element of a list 68 | { list-tail car } : list-ref 69 | // l -- ? 70 | { { dup null? { drop true true } { 71 | dup pair? { cdr false } { 72 | drop false true 73 | } cond } cond } until 74 | } : list? 75 | // l -- n 76 | { 0 { over null? not } { 1+ swap uncons nip swap } while nip 77 | } : list-length 78 | // l e -- t // returns tail of l after first member that satisfies e 79 | { swap { 80 | dup null? { nip true } { 81 | tuck car over execute { drop true } { 82 | swap cdr false 83 | } cond } cond } until 84 | } : list-tail-from 85 | // a l -- t // tail of l after first occurence of a using eq? 86 | { swap 1 ' eq? does list-tail-from } : list-member-eq 87 | { swap 1 ' eqv? does list-tail-from } : list-member-eqv 88 | { swap 1 ' equal? does list-tail-from } : list-member-equal 89 | // a l -- ? 90 | { list-member-eq null? not } : list-member? 91 | { list-member-eqv null? not } : list-member-eqv? 92 | // l -- a -1 or 0 // returns car l if l is non-empty 93 | { dup null? { drop false } { car true } cond 94 | } : safe-car 95 | { dup null? { drop false } { car second true } cond 96 | } : get-first-value 97 | // l e -- v -1 or 0 98 | { list-tail-from safe-car } : assoc-gen 99 | { list-tail-from get-first-value } : assoc-gen-x 100 | // a l -- (a.v) -1 or 0 -- returns first entry (a . v) in l 101 | { swap 1 { swap first eq? } does assoc-gen } : assq 102 | { swap 1 { swap first eqv? } does assoc-gen } : assv 103 | { swap 1 { swap first equal? } does assoc-gen } : assoc 104 | // a l -- v -1 or 0 -- returns v from first entry (a . v) in l 105 | { swap 1 { swap first eq? } does assoc-gen-x } : assq-val 106 | { swap 1 { swap first eqv? } does assoc-gen-x } : assv-val 107 | { swap 1 { swap first equal? } does assoc-gen-x } : assoc-val 108 | // (a1 .. an) e -- (e(a1) .. e(an)) 109 | recursive list-map { 110 | over null? { drop } { 111 | swap uncons -rot over execute -rot list-map cons 112 | } cond 113 | } swap ! 114 | 115 | variable ctxdump variable curctx 116 | // (a1 .. an) e -- executes e for a1, ..., an 117 | { ctxdump @ curctx @ ctxdump 2! curctx 2! 118 | { curctx 2@ over null? not } { swap uncons rot tuck curctx 2! execute } 119 | while 2drop ctxdump 2@ curctx ! ctxdump ! 120 | } : list-foreach 121 | forget ctxdump forget curctx 122 | 123 | // 124 | // Experimental implementation of `for` loops with index 125 | // 126 | variable loopdump variable curloop 127 | { curloop @ loopdump @ loopdump 2! } : push-loop-ctx 128 | { loopdump 2@ loopdump ! curloop ! } : pop-loop-ctx 129 | // ilast i0 e -- executes e for i=i0,i0+1,...,ilast-1 130 | { -rot 2dup > { 131 | push-loop-ctx { 132 | triple dup curloop ! first execute curloop @ untriple 1+ 2dup <= 133 | } until pop-loop-ctx 134 | } if 2drop drop 135 | } : for 136 | // ilast i0 e -- same as 'for', but pushes current index i before executing e 137 | { -rot 2dup > { 138 | push-loop-ctx { 139 | triple dup curloop ! untriple nip swap execute curloop @ untriple 1+ 2dup <= 140 | } until pop-loop-ctx 141 | } if 2drop drop 142 | } : for-i 143 | // ( -- i ) Returns innermost loop index 144 | { curloop @ third } : i 145 | // ( -- j ) Returns outer loop index 146 | { loopdump @ car third } : j 147 | { loopdump @ cadr third } : k 148 | forget curloop forget loopdump 149 | 150 | // 151 | // create Lisp-style lists using words "(" and ")" 152 | // 153 | variable ') 154 | 'nop box constant ', 155 | { ") without (" abort } ') ! 156 | { ') @ execute } : ) 157 | anon constant dot-marker 158 | // m x1 ... xn t m -- (x1 ... xn . t) 159 | { swap 160 | { -rot 2dup eq? not } 161 | { over dot-marker eq? abort"invalid dotted list" 162 | swap rot cons } while 2drop 163 | } : list-tail-until-marker 164 | // m x1 ... xn m -- (x1 ... xn) 165 | { null swap list-tail-until-marker } : list-until-marker 166 | { over dot-marker eq? { nip 2dup eq? abort"invalid dotted list" } 167 | { null swap } cond 168 | list-tail-until-marker 169 | } : list-until-marker-ext 170 | { ') @ ', @ } : ops-get 171 | { ', ! ') ! } : ops-set 172 | { anon dup ops-get 3 { ops-set list-until-marker-ext } does ') ! 'nop ', ! 173 | } : ( 174 | // test of Lisp-style lists 175 | // ( 42 ( `+ 9 ( `* 3 4 ) ) "test" ) .l cr 176 | // ( `eq? ( `* 3 4 ) 3 4 * ) .l cr 177 | // `alpha ( `beta `gamma `delta ) cons .l cr 178 | // { ( `eq? ( `* 3 5 pick ) 3 4 roll * ) } : 3*sample 179 | // 17 3*sample .l cr 180 | 181 | // similar syntax _( x1 .. xn ) for tuples 182 | { 2 { 1+ 2dup pick eq? } until 3 - nip } : count-to-marker 183 | { count-to-marker tuple nip } : tuple-until-marker 184 | { anon dup ops-get 3 { ops-set tuple-until-marker } does ') ! 'nop ', ! } : _( 185 | // test of tuples 186 | // _( _( 2 "two" ) _( 3 "three" ) _( 4 "four" ) ) .dump cr 187 | 188 | // pseudo-Lisp tokenizer 189 | "()[]'" 34 hold constant lisp-delims 190 | { lisp-delims 11 (word) } : lisp-token 191 | { null cons `quote swap cons } : do-quote 192 | { 1 { ', @ 2 { 2 { ', ! execute ', @ execute } does ', ! } 193 | does ', ! } does 194 | } : postpone-prefix 195 | { ', @ 1 { ', ! } does ', ! } : postpone-', 196 | ( `( ' ( pair 197 | `) ' ) pair 198 | `[ ' _( pair 199 | `] ' ) pair 200 | `' ' do-quote postpone-prefix pair 201 | `. ' dot-marker postpone-prefix pair 202 | `" { char " word } pair 203 | `;; { 0 word drop postpone-', } pair 204 | ) constant lisp-token-dict 205 | variable eol 206 | { eol @ eol 0! anon dup ') @ 'nop 3 207 | { ops-set list-until-marker-ext true eol ! } does ') ! rot ', ! 208 | { lisp-token dup (number) dup { roll drop } { 209 | drop atom dup lisp-token-dict assq { nip second execute } if 210 | } cond 211 | ', @ execute 212 | eol @ 213 | } until 214 | -rot eol ! execute 215 | } :_ List-generic( 216 | { 'nop 'nop List-generic( } :_ LIST( 217 | // LIST((lambda (x) (+ x 1)) (* 3 4)) 218 | // LIST('(+ 3 4)) 219 | // LIST(2 3 "test" . 9) 220 | // LIST((process '[plus 3 4])) 221 | -------------------------------------------------------------------------------- /token-contract/ft/jetton-wallet.fc: -------------------------------------------------------------------------------- 1 | ;; Jetton Wallet Smart Contract 2 | 3 | {- 4 | 5 | NOTE that this tokens can be transferred within the same workchain. 6 | 7 | This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions: 8 | 9 | 1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`) 10 | 11 | 2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain) 12 | 13 | -} 14 | 15 | int min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON 16 | int gas_consumption() asm "10000000 PUSHINT"; ;; 0.01 TON 17 | 18 | {- 19 | Storage 20 | storage#_ balance:Coins owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage; 21 | -} 22 | 23 | (int, slice, slice, cell) load_data() inline { 24 | slice ds = get_data().begin_parse(); 25 | return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref()); 26 | } 27 | 28 | () save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline { 29 | set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code)); 30 | } 31 | 32 | {- 33 | transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress 34 | response_destination:MsgAddress custom_payload:(Maybe ^Cell) 35 | forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) 36 | = InternalMsgBody; 37 | internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress 38 | response_address:MsgAddress 39 | forward_ton_amount:(VarUInteger 16) 40 | forward_payload:(Either Cell ^Cell) 41 | = InternalMsgBody; 42 | -} 43 | 44 | () send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { 45 | int query_id = in_msg_body~load_uint(64); 46 | int jetton_amount = in_msg_body~load_coins(); 47 | slice to_owner_address = in_msg_body~load_msg_addr(); 48 | force_chain(to_owner_address); 49 | (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); 50 | balance -= jetton_amount; 51 | 52 | throw_unless(705, equal_slices(owner_address, sender_address)); 53 | throw_unless(706, balance >= 0); 54 | 55 | cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code); 56 | slice to_wallet_address = calculate_jetton_wallet_address(state_init); 57 | slice response_address = in_msg_body~load_msg_addr(); 58 | cell custom_payload = in_msg_body~load_dict(); 59 | int forward_ton_amount = in_msg_body~load_coins(); 60 | throw_unless(708, slice_bits(in_msg_body) >= 1); 61 | slice either_forward_payload = in_msg_body; 62 | var msg = begin_cell() 63 | .store_uint(0x18, 6) 64 | .store_slice(to_wallet_address) 65 | .store_coins(0) 66 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 67 | .store_ref(state_init); 68 | var msg_body = begin_cell() 69 | .store_uint(op::internal_transfer(), 32) 70 | .store_uint(query_id, 64) 71 | .store_coins(jetton_amount) 72 | .store_slice(owner_address) 73 | .store_slice(response_address) 74 | .store_coins(forward_ton_amount) 75 | .store_slice(either_forward_payload) 76 | .end_cell(); 77 | 78 | msg = msg.store_ref(msg_body); 79 | int fwd_count = forward_ton_amount ? 2 : 1; 80 | throw_unless(709, msg_value > 81 | forward_ton_amount + 82 | ;; 3 messages: wal1->wal2, wal2->owner, wal2->response 83 | ;; but last one is optional (it is ok if it fails) 84 | fwd_count * fwd_fee + 85 | (2 * gas_consumption() + min_tons_for_storage())); 86 | ;; universal message send fee calculation may be activated here 87 | ;; by using this instead of fwd_fee 88 | ;; msg_fwd_fee(to_wallet, msg_body, state_init, 15) 89 | 90 | send_raw_message(msg.end_cell(), 64); ;; revert on errors 91 | save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); 92 | } 93 | 94 | {- 95 | internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress 96 | response_address:MsgAddress 97 | forward_ton_amount:(VarUInteger 16) 98 | forward_payload:(Either Cell ^Cell) 99 | = InternalMsgBody; 100 | -} 101 | 102 | () receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure { 103 | ;; NOTE we can not allow fails in action phase since in that case there will be 104 | ;; no bounce. Thus check and throw in computation phase. 105 | (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); 106 | int query_id = in_msg_body~load_uint(64); 107 | int jetton_amount = in_msg_body~load_coins(); 108 | balance += jetton_amount; 109 | slice from_address = in_msg_body~load_msg_addr(); 110 | slice response_address = in_msg_body~load_msg_addr(); 111 | throw_unless(707, 112 | equal_slices(jetton_master_address, sender_address) 113 | | 114 | equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address) 115 | ); 116 | int forward_ton_amount = in_msg_body~load_coins(); 117 | 118 | int ton_balance_before_msg = my_ton_balance - msg_value; 119 | int storage_fee = min_tons_for_storage() - min(ton_balance_before_msg, min_tons_for_storage()); 120 | msg_value -= (storage_fee + gas_consumption()); 121 | if(forward_ton_amount) { 122 | msg_value -= (forward_ton_amount + fwd_fee); 123 | slice either_forward_payload = in_msg_body; 124 | 125 | var msg_body = begin_cell() 126 | .store_uint(op::transfer_notification(), 32) 127 | .store_uint(query_id, 64) 128 | .store_coins(jetton_amount) 129 | .store_slice(from_address) 130 | .store_slice(either_forward_payload) 131 | .end_cell(); 132 | 133 | var msg = begin_cell() 134 | .store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract 135 | .store_slice(owner_address) 136 | .store_coins(forward_ton_amount) 137 | .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) 138 | .store_ref(msg_body); 139 | 140 | send_raw_message(msg.end_cell(), 1); 141 | } 142 | 143 | if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) { 144 | var msg = begin_cell() 145 | .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 146 | .store_slice(response_address) 147 | .store_coins(msg_value) 148 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 149 | .store_uint(op::excesses(), 32) 150 | .store_uint(query_id, 64); 151 | send_raw_message(msg.end_cell(), 2); 152 | } 153 | 154 | save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); 155 | } 156 | 157 | () burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { 158 | ;; NOTE we can not allow fails in action phase since in that case there will be 159 | ;; no bounce. Thus check and throw in computation phase. 160 | (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); 161 | int query_id = in_msg_body~load_uint(64); 162 | int jetton_amount = in_msg_body~load_coins(); 163 | slice response_address = in_msg_body~load_msg_addr(); 164 | ;; ignore custom payload 165 | ;; slice custom_payload = in_msg_body~load_dict(); 166 | balance -= jetton_amount; 167 | throw_unless(705, equal_slices(owner_address, sender_address)); 168 | throw_unless(706, balance >= 0); 169 | throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption()); 170 | 171 | var msg_body = begin_cell() 172 | .store_uint(op::burn_notification(), 32) 173 | .store_uint(query_id, 64) 174 | .store_coins(jetton_amount) 175 | .store_slice(owner_address) 176 | .store_slice(response_address) 177 | .end_cell(); 178 | 179 | var msg = begin_cell() 180 | .store_uint(0x18, 6) 181 | .store_slice(jetton_master_address) 182 | .store_coins(0) 183 | .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) 184 | .store_ref(msg_body); 185 | 186 | send_raw_message(msg.end_cell(), 64); 187 | 188 | save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); 189 | } 190 | 191 | () on_bounce (slice in_msg_body) impure { 192 | in_msg_body~skip_bits(32); ;; 0xFFFFFFFF 193 | (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); 194 | int op = in_msg_body~load_uint(32); 195 | throw_unless(709, (op == op::internal_transfer()) | (op == op::burn_notification())); 196 | int query_id = in_msg_body~load_uint(64); 197 | int jetton_amount = in_msg_body~load_coins(); 198 | balance += jetton_amount; 199 | save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); 200 | } 201 | 202 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 203 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 204 | return (); 205 | } 206 | 207 | slice cs = in_msg_full.begin_parse(); 208 | int flags = cs~load_uint(4); 209 | if (flags & 1) { 210 | on_bounce(in_msg_body); 211 | return (); 212 | } 213 | slice sender_address = cs~load_msg_addr(); 214 | cs~load_msg_addr(); ;; skip dst 215 | cs~load_coins(); ;; skip value 216 | cs~skip_bits(1); ;; skip extracurrency collection 217 | cs~load_coins(); ;; skip ihr_fee 218 | int fwd_fee = cs~load_coins(); ;; we use message fwd_fee for estimation of forward_payload costs 219 | 220 | int op = in_msg_body~load_uint(32); 221 | 222 | if (op == op::transfer()) { ;; outgoing transfer 223 | send_tokens(in_msg_body, sender_address, msg_value, fwd_fee); 224 | return (); 225 | } 226 | 227 | if (op == op::internal_transfer()) { ;; incoming transfer 228 | receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value); 229 | return (); 230 | } 231 | 232 | if (op == op::burn()) { ;; burn 233 | burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee); 234 | return (); 235 | } 236 | 237 | throw(0xffff); 238 | } 239 | 240 | (int, slice, slice, cell) get_wallet_data() method_id { 241 | return load_data(); 242 | } 243 | -------------------------------------------------------------------------------- /fift-libs/Stack.fif: -------------------------------------------------------------------------------- 1 | library Stack // advanced stack manupulation library 2 | "Lists.fif" include 3 | // S(a b c - a c 2 a b) would compile to code performing the requested stack manipulation 4 | 5 | // interface to low-level stack manipulation primitives 6 | { (number) 1- abort"index expected" dup 0 < over 255 > or 7 | abort"index 0..255 expected" 8 | } : (idx) 9 | // push(n) : a0 .. an - a0 .. an a0 equivalent to "n pick" 10 | // push(0) = dup, push(1) = over 11 | { 0 char ) word (idx) } ::_ push( 12 | // pop(n) : a0 a1 .. a(n-1) an - an a1 .. a(n-1) 13 | // pop(0) = drop, pop(1) = nip 14 | { 0 char ) word (idx) } ::_ pop( 15 | // xchg(i,j) : equivalent to "i j exch2" 16 | { 0 char , word (idx) char ) word (idx) } ::_ xchg( 17 | // xchg0(i) : equivalent to "i exch" or "xchg(0,i)" 18 | // xchg0(1) = swap 19 | { 0 char ) word (idx) 0 } ::_ xchg0( 20 | forget (idx) 21 | 22 | // parser for stack notation expressions 23 | ")" 34 hold +" -" constant stk-delims 24 | anon constant stk-start 25 | anon constant stk-to 26 | variable stk-mode 27 | { stk-delims 11 (word) } : stk-token 28 | 'nop : mk-lit 29 | // stk-start vn ... v0 -- stk-start ... v0 i where v[i]=v0 30 | { 0 { 31 | 1+ 2dup 2+ pick dup stk-start eq? { 2drop drop 0 true } { eqv? } cond 32 | } until 33 | } : stk-lookup 34 | // stk-start a1 .. an stk-to b1 .. bm -- [a1 .. an] [b1 .. bm] 35 | { stk-mode @ 0= abort"identifier expected" } : chk-lit 36 | { stk-to list-until-marker stk-mode ! 37 | stk-start list-until-marker stk-mode @ 38 | stk-mode 0! 39 | } : build-stk-effect 40 | { stk-start stk-mode 0! { 41 | stk-token dup ")" $= { drop true } { 42 | dup "-" $= { 43 | drop stk-mode @ abort"duplicate -" true stk-mode ! stk-to false } { 44 | dup 34 chr $= { chk-lit drop char " word mk-lit false } { 45 | dup (number) ?dup { chk-lit 1- { swap mk-lit -rot } if mk-lit nip false } { 46 | atom dup `_ eq? { stk-mode @ abort"identifier expected" false } { 47 | stk-lookup 0= stk-mode @ = { 48 | stk-mode @ { atom>$ +" -?" } { atom>$ +" redefined" } cond abort } { 49 | false 50 | } cond } cond } cond } cond } cond } cond } until 51 | stk-mode @ 0= abort"'-' expected" 52 | build-stk-effect 53 | } :_ parse-stk-list( 54 | 55 | // stack operation list construction 56 | variable op-rlist 57 | { op-rlist null! } : clear-op-list 58 | { op-rlist @ list-reverse } : get-op-list 59 | { op-rlist @ cons op-rlist ! } : issue-op 60 | { minmax `xchg -rot triple } : op-xchg 61 | { `push swap pair } : op-push 62 | { `lit swap pair } : op-lit 63 | { `pop swap pair } : op-pop 64 | 0 op-pop constant op-drop 65 | { 2dup <> { op-xchg issue-op } if } : issue-xchg 66 | { op-push issue-op } : issue-push 67 | { op-lit issue-op } : issue-lit 68 | { op-pop issue-op } : issue-pop 69 | { op-drop issue-op } : issue-drop 70 | { ' issue-drop swap times } : issue-drop-# 71 | 72 | // emulated stack contents 73 | variable emul-stk 74 | { emul-stk @ count } : emul-depth 75 | { emul-depth 1- swap - } : adj-i 76 | { emul-depth 1- tuck swap - swap rot - swap } : adj-ij 77 | // i j -- 78 | { adj-ij 2dup emul-stk @ tuck swap [] swap rot [] rot // i sj si j 79 | emul-stk @ -rot []= swap rot []= emul-stk ! 80 | } : emul-xchg 81 | { emul-stk @ tpop drop emul-stk ! } : emul-drop 82 | // i -- 83 | { 0 emul-xchg emul-drop } : emul-pop 84 | // i -- s[i] 85 | { emul-stk @ swap [] } : emul-stk[] 86 | // i -- si 87 | { adj-i emul-stk[] } : emul-get 88 | { 0 emul-get } : emul-tos 89 | // v i -- ? Check whether s[i]=v 90 | { dup emul-depth < { emul-stk[] eqv? } { 2drop false } cond } : emul[]-eq? 91 | // v -- i or -1 Returns maximum i with s[i]=v 92 | { emul-stk @ dup count { // v s i 93 | ?dup 0= { -1 true } { 1- 2dup [] 3 pick eqv? } cond // v s i' ? 94 | } until nip nip 95 | } : emul-stk-lookup-rev 96 | // i -- 97 | { emul-get emul-stk @ swap , emul-stk ! } : emul-push 98 | { emul-stk @ swap , emul-stk ! } : emul-lit 99 | // show emulated stack contents similarly to .s 100 | { emul-stk @ explode dup 1 reverse ' .l swap times cr } : .e 101 | 102 | // both issue an operation and emulate it 103 | { 2dup issue-xchg emul-xchg } : issue-emul-xchg 104 | { dup issue-push emul-push } : issue-emul-push 105 | { dup issue-lit emul-lit } : issue-emul-lit 106 | { dup issue-pop emul-pop } : issue-emul-pop 107 | { issue-drop emul-drop } : issue-emul-drop 108 | { ' issue-emul-drop swap times } : issue-emul-drop-# 109 | 110 | // b.. s -- b.. s moves tos value to stk[s] 111 | { dup emul-stk[] 2 pick cdr list-member-eqv? { 112 | dup adj-i 0 issue-emul-xchg } { dup adj-i issue-emul-pop } cond 113 | } : move-tos-to 114 | 115 | // new s -- ops registered 116 | { { over null? not } { 117 | // .sl .e get-op-list .l cr 118 | // get-op-list list-length 100 > abort"too long" 119 | emul-depth over > 120 | { over emul-tos swap list-member-eqv? not } { false } cond { 121 | // b.. s tos unneeded 122 | issue-emul-drop } { 123 | over car // b.. s b1 124 | 2dup swap emul[]-eq? { drop swap cdr swap 1+ } { 125 | dup emul-stk-lookup-rev // b.. s b1 i 126 | dup 0< { // b.. s b1 i not found, must be a literal 127 | drop dup atom? abort"unavailable value" 128 | issue-emul-lit } { 129 | dup 3 pick < { // b.. s b1 i found in bottom s stack values 130 | nip adj-i issue-emul-push // b.. s 131 | dup emul-depth 1- < { move-tos-to } if 132 | } { 133 | emul-depth 1- over = { // b.. s b1 i found in tos 134 | 2drop move-tos-to 135 | } { // b.. s b1 i 136 | nip over adj-ij issue-emul-xchg 137 | } cond } cond } cond } cond } cond } while 138 | nip emul-depth swap - issue-emul-drop-# 139 | } : generate-reorder-ops 140 | 141 | // old new -- op-list 142 | { emul-stk @ op-rlist @ 2swap 143 | swap list>tuple emul-stk ! clear-op-list 144 | 0 generate-reorder-ops get-op-list 145 | -rot op-rlist ! emul-stk ! 146 | } : generate-reorder 147 | { parse-stk-list( generate-reorder } :_ SG( 148 | 149 | // op-list rewriting according to a ruleset 150 | // l f l1 l2 -- l' -1 or l f with l' = l2 + (l - l1) 151 | { push(3) rot list- { list+ nip nip true } { drop } cond 152 | } : try-rule 153 | // l f ll -- l' -1 or l f 154 | { { dup null? not } { uncons 3 -roll unpair try-rule rot } while drop 155 | } : try-ruleset 156 | // l ll -- l' 157 | { swap { over false swap try-ruleset 0= } until nip 158 | } : try-ruleset* 159 | // l ruleset -- l' 160 | recursive try-ruleset*-everywhere { 161 | tuck try-ruleset* dup null? { nip } { 162 | uncons rot try-ruleset*-everywhere cons } cond 163 | } swap ! 164 | LIST( 165 | [([xchg 0 1] [xchg 0 2]) ([rot])] 166 | [([xchg 0 1] [xchg 1 2]) ([-rot])] 167 | [([xchg 0 2] [xchg 1 2]) ([rot])] 168 | [([xchg 0 2] [xchg 0 1]) ([-rot])] 169 | [([xchg 1 2] [xchg 0 1]) ([rot])] 170 | [([xchg 1 2] [xchg 0 2]) ([-rot])] 171 | [([xchg 0 1] [rot]) ([xchg 0 2])] 172 | [([-rot] [xchg 0 1]) ([xchg 0 2])] 173 | [([xchg 0 2] [xchg 1 3]) ([2swap])] 174 | [([xchg 1 3] [xchg 0 2]) ([2swap])] 175 | [([push 1] [push 1]) ([2dup])] 176 | [([push 3] [push 3]) ([2over])] 177 | [([pop 0] [pop 0]) ([2drop])] 178 | [([pop 1] [pop 0]) ([2drop])] 179 | [([xchg 0 1] [push 1]) ([tuck])] 180 | [([rot] [-rot]) ()] 181 | [([-rot] [rot]) ()] 182 | ) constant fift-stack-ruleset 183 | { fift-stack-ruleset try-ruleset*-everywhere } : fift-ops-rewrite 184 | { SG( fift-ops-rewrite } :_ SGF( 185 | 186 | // helpers for creating Fift source strings for one fift-op 187 | // i j -- s 188 | { minmax over { "xchg(" rot (.) $+ +"," swap (.) $+ +")" } 189 | { nip dup 1 = { drop "swap" } { 190 | ?dup { "xchg0(" swap (.) $+ +")" } { "" } cond 191 | } cond } cond 192 | } : source- 193 | // i -- s 194 | { dup 1 = { drop "over" } { 195 | ?dup { "push(" swap (.) $+ +")" } { "dup" } cond 196 | } cond 197 | } : source- 198 | // i -- s 199 | { dup 1 = { drop "nip" } { 200 | ?dup { "pop(" swap (.) $+ +")" } { "drop" } cond 201 | } cond 202 | } : source- 203 | // lit -- s 204 | { dup string? { char " chr swap $+ char " hold } { (.) } cond 205 | } : source- 206 | 207 | // dictionary with all fift op compilation/source creation 208 | { 0 swap (compile) } : fop-compile 209 | ( _( `xchg 2 { fop-compile } { source- swap cons } ) 210 | _( `push 1 { fop-compile } { source- swap cons } ) 211 | _( `pop 1 { fop-compile } { source- swap cons } ) 212 | _( `lit 1 { 1 'nop (compile) } { source- swap cons } ) 213 | _( `rot 0 { ' rot fop-compile } { "rot" swap cons } ) 214 | _( `-rot 0 { ' -rot fop-compile } { "-rot" swap cons } ) 215 | _( `tuck 0 { ' tuck fop-compile } { "tuck" swap cons } ) 216 | _( `2swap 0 { ' 2swap fop-compile } { "2swap" swap cons } ) 217 | _( `2drop 0 { ' 2drop fop-compile } { "2drop" swap cons } ) 218 | _( `2dup 0 { ' 2dup fop-compile } { "2dup" swap cons } ) 219 | _( `2over 0 { ' 2over fop-compile } { "2over" swap cons } ) 220 | ) box constant fift-op-dict 221 | 222 | { dup atom? { atom>$ } { drop "" } cond 223 | "unknown operation " swap $+ abort 224 | } : report-unknown-op 225 | variable 'fop-entry-exec 226 | // process fift-op according to 'fop-entry-exec 227 | // ... op - ... 228 | { dup first dup fift-op-dict @ assq { report-unknown-op } ifnot 229 | dup second 1+ push(3) count <> abort"incorrect param count" 230 | nip swap explode dup roll drop 1- roll // o2 .. on entry 231 | 'fop-entry-exec @ execute 232 | } : process-fift-op 233 | 234 | // compile op-list into Fift wordlist 235 | // wl op-list -- wl' 236 | { { third execute } 'fop-entry-exec ! 237 | swap ' process-fift-op foldl } : compile-fift-op* 238 | // op-list -- e 239 | { fift-ops-rewrite ({) swap compile-fift-op* (}) } : ops>wdef 240 | 241 | // S( - ) compiles a "word" performing required action 242 | { SG( ops>wdef 0 swap } ::_ S( 243 | // 1 2 3 S(a b c - c a b a) .s would print 3 1 2 1 244 | 245 | // transform op-list into Fift source 246 | // ls op -- ls' 247 | { fift-ops-rewrite 248 | { 3 [] execute } 'fop-entry-exec ! 249 | null ' process-fift-op foldl 250 | dup null? { drop "" } { { +" " swap $+ } foldr-ne } cond 251 | } : ops>$ 252 | { SG( ops>$ 1 'nop } ::_ $S( 253 | { SG( ops>$ type } :_ .$S( 254 | // $S(a b c - b c a c a c) => string "rot 2dup over" 255 | // S(a b c - b c a c a c) => compile/execute block { rot 2dup over } 256 | // $S(_ x y _ - y x) => string "drop pop(2)" 257 | // .$S(x1 x2 - 17 x1) => print string "drop 17 swap" 258 | 259 | // simplify/transform sequences of stack manipulation operations 260 | LIST(. [a b c d e f g h i j]) constant std-stack 261 | { stk-start std-stack explode drop stk-to std-stack explode drop 262 | } : simplify<{ 263 | { build-stk-effect generate-reorder ops>$ } : }>stack 264 | // simplify<{ drop drop over over -13 }>stack => string "2drop 2dup -13" 265 | // simplify<{ 17 rot }>stack => string "swap 17 swap" 266 | // simplify<{ 5 1 reverse }>stack => string "xchg(1,5) xchg(2,4)" 267 | -------------------------------------------------------------------------------- /token-contract/ft/build/jetton-minter.fif: -------------------------------------------------------------------------------- 1 | "Asm.fif" include 2 | // automatically generated from `../stdlib.fc` `params.fc` `op-codes.fc` `jetton-utils.fc` `jetton-minter.fc` 3 | PROGRAM{ 4 | DECLPROC force_chain 5 | DECLPROC pack_jetton_wallet_data 6 | DECLPROC calculate_jetton_wallet_state_init 7 | DECLPROC calculate_jetton_wallet_address 8 | DECLPROC calculate_user_jetton_wallet_address 9 | DECLPROC load_data 10 | DECLPROC save_data 11 | DECLPROC mint_tokens 12 | DECLPROC recv_internal 13 | 106029 DECLMETHOD get_jetton_data 14 | 103289 DECLMETHOD get_wallet_address 15 | force_chain PROC:<{ 16 | // addr 17 | REWRITESTDADDR // _8 _9 18 | DROP // wc 19 | 0 PUSHINT // wc _5 20 | EQUAL // _6 21 | 333 THROWIFNOT 22 | }> 23 | pack_jetton_wallet_data PROC:<{ 24 | // balance owner_address jetton_master_address jetton_wallet_code 25 | NEWC // balance owner_address jetton_master_address jetton_wallet_code _4 26 | s0 s4 XCHG2 // jetton_wallet_code owner_address jetton_master_address _4 balance 27 | STVARUINT16 // jetton_wallet_code owner_address jetton_master_address _5 28 | ROT // jetton_wallet_code jetton_master_address _5 owner_address 29 | STSLICER // jetton_wallet_code jetton_master_address _6 30 | SWAP // jetton_wallet_code _6 jetton_master_address 31 | STSLICER // jetton_wallet_code _7 32 | STREF // _8 33 | ENDC // _9 34 | }> 35 | calculate_jetton_wallet_state_init PROC:<{ 36 | // owner_address jetton_master_address jetton_wallet_code 37 | 0 PUSHINT // owner_address jetton_master_address jetton_wallet_code _3=0 38 | s0 s1 s2 XCPUXC 39 | s4 s0 s3 XC2PU // _3=0 jetton_wallet_code _4=0 owner_address jetton_master_address jetton_wallet_code 40 | pack_jetton_wallet_data INLINECALLDICT // _3=0 jetton_wallet_code _5 41 | s2 PUSH // _3=0 jetton_wallet_code _5 _6=0 42 | NEWC // _3=0 jetton_wallet_code _5 _6=0 _7 43 | 2 STU // _3=0 jetton_wallet_code _5 _9 44 | s1 s2 XCHG // _3=0 _5 jetton_wallet_code _9 45 | STDICT // _3=0 _5 _10 46 | STDICT // _3=0 _11 47 | 1 STU // _13 48 | ENDC // _14 49 | }> 50 | calculate_jetton_wallet_address PROC:<{ 51 | // state_init 52 | HASHCU // _1 53 | 0 PUSHINT // _1 _2 54 | 4 PUSHINT // _1 _2 _3=4 55 | NEWC // _1 _2 _3=4 _4 56 | 3 STU // _1 _2 _6 57 | 8 STI // _1 _8 58 | 256 STU // _10 59 | ENDC // _11 60 | CTOS // _12 61 | }> 62 | calculate_user_jetton_wallet_address PROC:<{ 63 | // owner_address jetton_master_address jetton_wallet_code 64 | calculate_jetton_wallet_state_init INLINECALLDICT // _3 65 | calculate_jetton_wallet_address INLINECALLDICT // _4 66 | }> 67 | load_data PROC:<{ 68 | // 69 | c4 PUSH // _1 70 | CTOS // ds 71 | LDVARUINT16 // _3 ds 72 | LDMSGADDR // _3 _5 ds 73 | LDREF // _3 _5 _7 ds 74 | LDREF // _3 _5 _7 _18 _17 75 | DROP // _3 _5 _7 _9 76 | }> 77 | save_data PROC:<{ 78 | // total_supply admin_address content jetton_wallet_code 79 | NEWC // total_supply admin_address content jetton_wallet_code _4 80 | s0 s4 XCHG2 // jetton_wallet_code admin_address content _4 total_supply 81 | STVARUINT16 // jetton_wallet_code admin_address content _5 82 | ROT // jetton_wallet_code content _5 admin_address 83 | STSLICER // jetton_wallet_code content _6 84 | STREF // jetton_wallet_code _7 85 | STREF // _8 86 | ENDC // _9 87 | c4 POP 88 | }> 89 | mint_tokens PROC:<{ 90 | // to_address jetton_wallet_code amount master_msg 91 | MYADDR // to_address jetton_wallet_code amount master_msg _5 92 | s4 s0 s3 XCHG3 // amount master_msg to_address _5 jetton_wallet_code 93 | calculate_jetton_wallet_state_init INLINECALLDICT // amount master_msg state_init 94 | DUP // amount master_msg state_init state_init 95 | calculate_jetton_wallet_address INLINECALLDICT // amount master_msg state_init to_wallet_address 96 | 7 PUSHINT // amount master_msg state_init to_wallet_address _14 97 | 24 PUSHINT // amount master_msg state_init to_wallet_address _14 _15=24 98 | NEWC // amount master_msg state_init to_wallet_address _14 _15=24 _16 99 | 6 STU // amount master_msg state_init to_wallet_address _14 _18 100 | ROT // amount master_msg state_init _14 _18 to_wallet_address 101 | STSLICER // amount master_msg state_init _14 _19 102 | s0 s4 XCHG2 // _14 master_msg state_init _19 amount 103 | STVARUINT16 // _14 master_msg state_init _20 104 | s1 s3 XCHG // state_init master_msg _14 _20 105 | 108 STU // state_init master_msg _36 106 | s1 s2 XCHG // master_msg state_init _36 107 | STREF // master_msg _37 108 | STREF // msg 109 | ENDC // _39 110 | 1 PUSHINT // _39 _40=1 111 | SENDRAWMSG 112 | }> 113 | recv_internal PROC:<{ 114 | // msg_value in_msg_full in_msg_body 115 | s2 POP // in_msg_body in_msg_full 116 | OVER // in_msg_body in_msg_full in_msg_body 117 | SEMPTY // in_msg_body in_msg_full _3 118 | IFJMP:<{ // in_msg_body in_msg_full 119 | 2DROP // 120 | }> // in_msg_body in_msg_full 121 | CTOS // in_msg_body cs 122 | 4 LDU // in_msg_body flags cs 123 | SWAP 124 | 1 PUSHINT // in_msg_body cs flags _10=1 125 | AND // in_msg_body cs _11 126 | IFJMP:<{ // in_msg_body cs 127 | 2DROP // 128 | }> // in_msg_body cs 129 | LDMSGADDR // in_msg_body _132 _131 130 | DROP // in_msg_body sender_address 131 | SWAP // sender_address in_msg_body 132 | 32 LDU // sender_address op in_msg_body 133 | 64 LDU // sender_address op query_id in_msg_body 134 | load_data INLINECALLDICT // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code 135 | 21 PUSHINT // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code _28 136 | s7 s(-1) PUXC // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code op _28 137 | EQUAL // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code _29 138 | IFJMP:<{ // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code 139 | s5 POP 140 | s5 POP // sender_address content jetton_wallet_code in_msg_body total_supply admin_address 141 | s5 s5 XCPU // admin_address content jetton_wallet_code in_msg_body total_supply sender_address admin_address 142 | SDEQ // admin_address content jetton_wallet_code in_msg_body total_supply _31 143 | 73 THROWIFNOT 144 | SWAP // admin_address content jetton_wallet_code total_supply in_msg_body 145 | LDMSGADDR // admin_address content jetton_wallet_code total_supply to_address in_msg_body 146 | LDVARUINT16 // admin_address content jetton_wallet_code total_supply to_address amount in_msg_body 147 | LDREF // admin_address content jetton_wallet_code total_supply to_address amount _146 _145 148 | DROP // admin_address content jetton_wallet_code total_supply to_address amount master_msg 149 | DUP // admin_address content jetton_wallet_code total_supply to_address amount master_msg master_msg 150 | CTOS // admin_address content jetton_wallet_code total_supply to_address amount master_msg master_msg_cs 151 | 96 PUSHINT // admin_address content jetton_wallet_code total_supply to_address amount master_msg master_msg_cs _47 152 | SDSKIPFIRST // admin_address content jetton_wallet_code total_supply to_address amount master_msg master_msg_cs 153 | LDVARUINT16 // admin_address content jetton_wallet_code total_supply to_address amount master_msg _148 _147 154 | DROP // admin_address content jetton_wallet_code total_supply to_address amount master_msg jetton_amount 155 | s5 PUSH 156 | s3 s4 XCHG 157 | s4 s2 XCHG2 // admin_address content jetton_wallet_code total_supply jetton_amount to_address jetton_wallet_code amount master_msg 158 | mint_tokens CALLDICT 159 | ADD // admin_address content jetton_wallet_code _53 160 | 3 -ROLL // _53 admin_address content jetton_wallet_code 161 | save_data INLINECALLDICT 162 | }> // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code 163 | 0x7bdd97de PUSHINT // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code _55 164 | s7 s(-1) PUXC // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code op _55 165 | EQUAL // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code _56 166 | IFJMP:<{ // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code 167 | s6 POP // sender_address jetton_wallet_code query_id in_msg_body total_supply admin_address content 168 | s0 s3 XCHG // sender_address jetton_wallet_code query_id content total_supply admin_address in_msg_body 169 | LDVARUINT16 // sender_address jetton_wallet_code query_id content total_supply admin_address jetton_amount in_msg_body 170 | LDMSGADDR // sender_address jetton_wallet_code query_id content total_supply admin_address jetton_amount from_address in_msg_body 171 | MYADDR // sender_address jetton_wallet_code query_id content total_supply admin_address jetton_amount from_address in_msg_body _64 172 | s2 s0 s8 XC2PU // sender_address jetton_wallet_code query_id content total_supply admin_address jetton_amount in_msg_body from_address _64 jetton_wallet_code 173 | calculate_user_jetton_wallet_address INLINECALLDICT // sender_address jetton_wallet_code query_id content total_supply admin_address jetton_amount in_msg_body _65 174 | s0 s8 XCHG2 // in_msg_body jetton_wallet_code query_id content total_supply admin_address jetton_amount _65 sender_address 175 | SDEQ // in_msg_body jetton_wallet_code query_id content total_supply admin_address jetton_amount _66 176 | 74 THROWIFNOT 177 | s1 s2 XCHG // in_msg_body jetton_wallet_code query_id content admin_address total_supply jetton_amount 178 | SUB // in_msg_body jetton_wallet_code query_id content admin_address _68 179 | s0 s3 XCHG 180 | s2 s4 XCHG2 // in_msg_body query_id _68 admin_address content jetton_wallet_code 181 | save_data INLINECALLDICT 182 | SWAP // query_id in_msg_body 183 | LDMSGADDR // query_id _154 _153 184 | DROP // query_id response_address 185 | DUP // query_id response_address response_address 186 | 2 PLDU // query_id response_address _74 187 | 0 NEQINT // query_id response_address _76 188 | IF:<{ // query_id response_address 189 | 0xd53276db PUSHINT // query_id response_address _78 190 | 0 PUSHINT // query_id response_address _78 _79=0 191 | 16 PUSHINT // query_id response_address _78 _79=0 _80=16 192 | NEWC // query_id response_address _78 _79=0 _80=16 _81 193 | 6 STU // query_id response_address _78 _79=0 _83 194 | s0 s3 XCHG2 // query_id _79=0 _78 _83 response_address 195 | STSLICER // query_id _79=0 _78 _84 196 | s2 PUSH // query_id _79=0 _78 _84 _85=0 197 | STVARUINT16 // query_id _79=0 _78 _86 198 | s1 s2 XCHG // query_id _78 _79=0 _86 199 | 107 STU // query_id _78 _100 200 | 32 STU // query_id _102 201 | 64 STU // msg 202 | ENDC // _105 203 | 66 PUSHINT // _105 _108 204 | SENDRAWMSG 205 | }>ELSE<{ 206 | 2DROP // 207 | }> 208 | }> // sender_address op query_id in_msg_body total_supply admin_address content jetton_wallet_code 209 | s5 POP // sender_address op jetton_wallet_code in_msg_body total_supply admin_address content 210 | s5 PUSH // sender_address op jetton_wallet_code in_msg_body total_supply admin_address content op 211 | 3 EQINT // sender_address op jetton_wallet_code in_msg_body total_supply admin_address content _111 212 | IFJMP:<{ // sender_address op jetton_wallet_code in_msg_body total_supply admin_address content 213 | s5 POP // sender_address content jetton_wallet_code in_msg_body total_supply admin_address 214 | s1 s5 XCHG // total_supply content jetton_wallet_code in_msg_body sender_address admin_address 215 | SDEQ // total_supply content jetton_wallet_code in_msg_body _113 216 | 73 THROWIFNOT 217 | LDMSGADDR // total_supply content jetton_wallet_code _156 _155 218 | DROP // total_supply content jetton_wallet_code new_admin_address 219 | -ROT // total_supply new_admin_address content jetton_wallet_code 220 | save_data INLINECALLDICT 221 | }> // sender_address op jetton_wallet_code in_msg_body total_supply admin_address content 222 | DROP // sender_address op jetton_wallet_code in_msg_body total_supply admin_address 223 | s0 s4 XCHG // sender_address admin_address jetton_wallet_code in_msg_body total_supply op 224 | 4 EQINT // sender_address admin_address jetton_wallet_code in_msg_body total_supply _120 225 | IFJMP:<{ // sender_address admin_address jetton_wallet_code in_msg_body total_supply 226 | s4 s3 XCPU // total_supply admin_address jetton_wallet_code in_msg_body sender_address admin_address 227 | SDEQ // total_supply admin_address jetton_wallet_code in_msg_body _122 228 | 73 THROWIFNOT 229 | LDREF // total_supply admin_address jetton_wallet_code _158 _157 230 | DROP // total_supply admin_address jetton_wallet_code _124 231 | SWAP // total_supply admin_address _124 jetton_wallet_code 232 | save_data INLINECALLDICT 233 | }> // sender_address admin_address jetton_wallet_code in_msg_body total_supply 234 | 5 BLKDROP // 235 | 16 PUSHPOW2DEC // _127=65535 236 | THROWANY 237 | }> 238 | get_jetton_data PROC:<{ 239 | // 240 | load_data INLINECALLDICT // total_supply admin_address content jetton_wallet_code 241 | -1 PUSHINT // total_supply admin_address content jetton_wallet_code _5=-1 242 | 3 -ROLL // total_supply _5=-1 admin_address content jetton_wallet_code 243 | }> 244 | get_wallet_address PROC:<{ 245 | // owner_address 246 | load_data INLINECALLDICT // owner_address _8 _9 _10 _11 247 | 3 1 BLKDROP2 // owner_address jetton_wallet_code 248 | MYADDR // owner_address jetton_wallet_code _6 249 | SWAP // owner_address _6 jetton_wallet_code 250 | calculate_user_jetton_wallet_address INLINECALLDICT // _7 251 | }> 252 | }END>c 253 | -------------------------------------------------------------------------------- /token-contract/stdlib.fc: -------------------------------------------------------------------------------- 1 | ;; Standard library for funC 2 | ;; 3 | 4 | forall X -> tuple cons(X head, tuple tail) asm "CONS"; 5 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; 6 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; 7 | forall X -> X car(tuple list) asm "CAR"; 8 | tuple cdr(tuple list) asm "CDR"; 9 | tuple empty_tuple() asm "NIL"; 10 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; 11 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; 12 | forall X -> [X] single(X x) asm "SINGLE"; 13 | forall X -> X unsingle([X] t) asm "UNSINGLE"; 14 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; 15 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; 16 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; 17 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; 18 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; 19 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; 20 | forall X -> X first(tuple t) asm "FIRST"; 21 | forall X -> X second(tuple t) asm "SECOND"; 22 | forall X -> X third(tuple t) asm "THIRD"; 23 | forall X -> X fourth(tuple t) asm "3 INDEX"; 24 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; 25 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; 26 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; 27 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; 28 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; 29 | forall X -> X null() asm "PUSHNULL"; 30 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; 31 | 32 | int now() asm "NOW"; 33 | slice my_address() asm "MYADDR"; 34 | [int, cell] get_balance() asm "BALANCE"; 35 | int cur_lt() asm "LTIME"; 36 | int block_lt() asm "BLOCKLT"; 37 | 38 | int cell_hash(cell c) asm "HASHCU"; 39 | int slice_hash(slice s) asm "HASHSU"; 40 | int string_hash(slice s) asm "SHA256U"; 41 | 42 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; 43 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; 44 | 45 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; 46 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; 47 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 48 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 49 | 50 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; 51 | 52 | () dump_stack() impure asm "DUMPSTK"; 53 | 54 | cell get_data() asm "c4 PUSH"; 55 | () set_data(cell c) impure asm "c4 POP"; 56 | cont get_c3() impure asm "c3 PUSH"; 57 | () set_c3(cont c) impure asm "c3 POP"; 58 | cont bless(slice s) impure asm "BLESS"; 59 | 60 | () accept_message() impure asm "ACCEPT"; 61 | () set_gas_limit(int limit) impure asm "SETGASLIMIT"; 62 | () commit() impure asm "COMMIT"; 63 | () buy_gas(int gram) impure asm "BUYGAS"; 64 | 65 | int min(int x, int y) asm "MIN"; 66 | int max(int x, int y) asm "MAX"; 67 | (int, int) minmax(int x, int y) asm "MINMAX"; 68 | int abs(int x) asm "ABS"; 69 | 70 | slice begin_parse(cell c) asm "CTOS"; 71 | () end_parse(slice s) impure asm "ENDS"; 72 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; 73 | cell preload_ref(slice s) asm "PLDREF"; 74 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; 75 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; 76 | ;; int preload_int(slice s, int len) asm "PLDIX"; 77 | ;; int preload_uint(slice s, int len) asm "PLDUX"; 78 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; 79 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; 80 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; 81 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; 82 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; 83 | slice first_bits(slice s, int len) asm "SDCUTFIRST"; 84 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 85 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 86 | slice slice_last(slice s, int len) asm "SDCUTLAST"; 87 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; 88 | cell preload_dict(slice s) asm "PLDDICT"; 89 | slice skip_dict(slice s) asm "SKIPDICT"; 90 | 91 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; 92 | cell preload_maybe_ref(slice s) asm "PLDOPTREF"; 93 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; 94 | 95 | int cell_depth(cell c) asm "CDEPTH"; 96 | 97 | int slice_refs(slice s) asm "SREFS"; 98 | int slice_bits(slice s) asm "SBITS"; 99 | (int, int) slice_bits_refs(slice s) asm "SBITREFS"; 100 | int slice_empty?(slice s) asm "SEMPTY"; 101 | int slice_data_empty?(slice s) asm "SDEMPTY"; 102 | int slice_refs_empty?(slice s) asm "SREMPTY"; 103 | int slice_depth(slice s) asm "SDEPTH"; 104 | 105 | int builder_refs(builder b) asm "BREFS"; 106 | int builder_bits(builder b) asm "BBITS"; 107 | int builder_depth(builder b) asm "BDEPTH"; 108 | 109 | builder begin_cell() asm "NEWC"; 110 | cell end_cell(builder b) asm "ENDC"; 111 | builder store_ref(builder b, cell c) asm(c b) "STREF"; 112 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; 113 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; 114 | builder store_slice(builder b, slice s) asm "STSLICER"; 115 | builder store_grams(builder b, int x) asm "STGRAMS"; 116 | builder store_dict(builder b, cell c) asm(c b) "STDICT"; 117 | 118 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; 119 | tuple parse_addr(slice s) asm "PARSEMSGADDR"; 120 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; 121 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; 122 | 123 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 124 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 125 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 126 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 127 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; 128 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; 129 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; 130 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; 131 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; 132 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; 133 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; 134 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; 135 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; 136 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 137 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 138 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 139 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 140 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 141 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 142 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 143 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 144 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 145 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 146 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; 147 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; 148 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; 149 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; 150 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 151 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 152 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 153 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 154 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 155 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 156 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; 157 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; 158 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; 159 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; 160 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 161 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 162 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 163 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 164 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 165 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 166 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 167 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 168 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 169 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 170 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 171 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 172 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; 173 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; 174 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; 175 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; 176 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; 177 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; 178 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; 179 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; 180 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; 181 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; 182 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; 183 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; 184 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; 185 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; 186 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; 187 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; 188 | cell new_dict() asm "NEWDICT"; 189 | int dict_empty?(cell c) asm "DICTEMPTY"; 190 | 191 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; 192 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; 193 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; 194 | 195 | cell config_param(int x) asm "CONFIGOPTPARAM"; 196 | int cell_null?(cell c) asm "ISNULL"; 197 | 198 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; 199 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; 200 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; 201 | () set_code(cell new_code) impure asm "SETCODE"; 202 | 203 | int random() impure asm "RANDU256"; 204 | int rand(int range) impure asm "RAND"; 205 | int get_seed() impure asm "RANDSEED"; 206 | int set_seed() impure asm "SETRAND"; 207 | () randomize(int x) impure asm "ADDRAND"; 208 | () randomize_lt() impure asm "LTIME" "ADDRAND"; 209 | 210 | builder store_coins(builder b, int x) asm "STVARUINT16"; 211 | (slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16"; 212 | 213 | int equal_slices (slice a, slice b) asm "SDEQ"; 214 | int builder_null?(builder b) asm "ISNULL"; 215 | builder store_builder(builder to, builder from) asm "STBR"; 216 | 217 | -------------------------------------------------------------------------------- /fift-libs/TonUtil.fif: -------------------------------------------------------------------------------- 1 | library TonUtil // TON Blockchain Fift Library 2 | "Lists.fif" include 3 | 4 | -1 constant Masterchain 5 | 0 constant Basechain 6 | 7 | // parse workchain id 8 | // ( S -- workchain ) 9 | { (number) 1- abort"workchain id must be an integer" 10 | dup 32 fits not abort"workchain id must fit in 32 bits" 11 | } : parse-workchain-id 12 | 13 | { (number) 1- abort"integer expected" } : parse-int 14 | 15 | { over null? ' swap if drop } : replace-if-null 16 | 17 | // Private key load/generate 18 | // ( fname -- pubkey privkey ) 19 | { dup ."Loading private key from file " type cr 20 | file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long" 21 | dup priv>pub swap 22 | } : load-keypair 23 | // ( fname -- pubkey privkey ) 24 | { dup file-exists? 25 | { load-keypair } 26 | { dup newkeypair swap rot over swap B>file 27 | rot ."Saved new private key to file " type cr 28 | } cond 29 | } : load-generate-keypair 30 | 31 | // Parse smart-contract address 32 | // ( S -- workchain addr bounce? ) 33 | { $>smca not abort"invalid smart-contract address" 34 | 1 and 0= 35 | } : parse-smc-addr 36 | 37 | // ( x -- ) Displays a 64-digit hex number 38 | { 64 0x. } : 64x. 39 | { 64 0X. } : 64X. 40 | // ( wc addr -- ) Show address in : form 41 | { swap ._ .":" 64x. } : .addr 42 | // ( wc addr flags -- ) Show address in base64url form 43 | { smca>$ type } : .Addr 44 | // ( wc addr fname -- ) Save address to file in 36-byte format 45 | { -rot 256 u>B swap 32 i>B B+ swap B>file } : save-address 46 | // ( wc addr fname -- ) Save address and print message 47 | { dup ."(Saving address to file " type .")" cr save-address 48 | } : save-address-verbose 49 | 50 | // ( fname -- wc addr ) Load address from file 51 | { file>B 32 B| 52 | dup Blen { 32 B>i@ } { drop Basechain } cond 53 | swap 256 B>u@ 54 | } : load-address 55 | // ( fname -- wc addr ) Load address from file and print message 56 | { dup ."(Loading address from file " type .")" cr load-address 57 | } : load-address-verbose 58 | // Parse string as address or load address from file (if string is prefixed by @) 59 | // ( S default-bounce -- workchain addr bounce? ) 60 | { over $len 0= abort"empty smart-contract address" 61 | swap dup 1 $| swap "@" $= 62 | { nip load-address rot } { drop nip parse-smc-addr } cond 63 | } : parse-load-address 64 | 65 | // ( hex-str -- addr ) Parses ADNL address 66 | { dup $len 64 <> abort"ADNL address must consist of exactly 64 hexadecimal characters" 67 | (hex-number) 1 <> abort"ADNL address must consist of 64 hexadecimal characters" 68 | dup 256 ufits not abort"invalid ADNL address" 69 | } : parse-adnl-address 70 | 71 | // ( b wc addr -- b' ) Serializes address into Builder b 72 | { -rot 8 i, swap 256 u, } : addr, 73 | { over 8 fits { rot b{100} s, -rot addr, } { 74 | rot b{110} s, 256 9 u, rot 32 i, swap 256 u, } cond 75 | } : Addr, 76 | 77 | // Gram utilities 78 | 1000000000 constant Gram 79 | { Gram swap */r } : Gram*/ 80 | { Gram * } : Gram* 81 | { (number) dup { 1- ' Gram*/ ' Gram* cond true } if 82 | } : $>GR? 83 | // ( S -- nanograms ) 84 | { $>GR? not abort"not a valid Gram amount" 85 | } : $>GR 86 | { bl word $>GR 1 'nop } ::_ GR$ 87 | // ( nanograms -- S ) 88 | { dup abs <# ' # 9 times char . hold #s rot sign #> 89 | nip -trailing0 } : (.GR) 90 | { (.GR) ."GR$" type } : .GR_ 91 | { .GR_ space } : .GR 92 | 93 | // b x -- b' ( serializes a Gram amount ) 94 | { -1 { 1+ 2dup 8 * ufits } until 95 | rot over 4 u, -rot 8 * u, } : Gram, 96 | // s -- x s' ( deserializes a Gram amount ) 97 | { 4 u@+ swap 8 * u@+ } : Gram@+ 98 | // s -- x 99 | { 4 u@+ swap 8 * u@ } : Gram@ 100 | 101 | // currency collections 102 | // b x --> b' ( serializes a VarUInteger32 ) 103 | { -1 { 1+ 2dup 8 * ufits } until 104 | rot over 5 u, -rot 8 * u, } : VarUInt32, 105 | // s --> x ( deserializes a VarUInteger32 ) 106 | { 5 u@+ swap 8 * u@ } : VarUInt32@ 107 | 32 constant cc-key-bits 108 | ' VarUInt32, : val, 109 | ' VarUInt32@ : val@ 110 | // d k v -- d' 111 | { cc 120 | { dup null? { ."(null)" drop } { val@ ._ } cond } dup : .maybeVarUInt32 : .val 121 | { swap cc-key-bits { rot { ."+" } if .val ."*$" ._ true true } idictforeach drop } : (.cc) 122 | { false (.cc) { ."0" } ifnot } : .cc_ 123 | { .cc_ space } : .cc 124 | { true (.cc) drop } : .+cc_ 125 | { .+cc_ space } : .+cc 126 | { cc-key-bits { rot . ."-> " swap .val .val ."; " true } dictdiff drop cr } : show-cc-diff 127 | { cc-key-bits { val@ swap val@ + val, true } dictmerge } : cc+ 128 | { null swap cc-key-bits { val@ pair swap cons true } idictforeach drop } : cc>list-rev 129 | { cc>list-rev list-reverse } : cc>list 130 | forget val, forget val@ forget .val 131 | 132 | // ( S -- x -1 or 0 ) 133 | { (number) dup 2 = { -rot 2drop } if 1 = } : int? 134 | { int? dup { drop dup 0< { drop false } { true } cond } if } : pos-int? 135 | // ( S -- k v -1 or 0 ) Parses expression * or *$ 136 | { dup "*" $pos dup 0< { 2drop false } { 137 | $| dup $len 2 < { 2drop false } { 138 | 1 $| nip dup 1 $| swap "$" $= { swap } if drop 139 | int? dup { over 32 fits { 2drop false } ifnot } if 140 | not { drop false } { 141 | swap pos-int? not { drop false } { 142 | true 143 | } cond } cond } cond } cond 144 | } : cc-key-value? 145 | // ( S -- D -1 or 0 ) Parses an extra currency collection 146 | // e.g. "10000*$3+7777*$-11" means "10000 units of currency #3 and 7777 units of currency #-11" 147 | { dictnew { // S D 148 | swap dup "+" $pos dup 0< { drop null -rot } { $| 1 $| nip -rot } cond 149 | cc-key-value? { +ccpair over null? dup { rot drop true } if } { 2drop false true } cond 150 | } until 151 | } : $>xcc? 152 | { $>xcc? not abort"invalid extra currency collection" } : $>xcc 153 | { char } word dup $len { $>xcc } { drop dictnew } cond 1 'nop } ::_ CX{ 154 | 155 | // complete currency collections 156 | { $>xcc? { true } { drop false } cond } : end-parse-cc 157 | // ( S -- x D -1 or 0 ) Parses a currency collection 158 | // e.g. "1.2+300*$2" means "1200000000ng plus 300 units of currency #2" 159 | { 0 swap dup "+" $pos dup 0< { drop dup 160 | $>GR? { nip nip dictnew true } { end-parse-cc } cond 161 | } { over swap $| swap $>GR? { 2swap 2drop swap 1 $| nip } { drop 162 | } cond end-parse-cc } cond 163 | } : $>cc? 164 | { $>cc? not abort"invalid currency collection" } : $>cc 165 | { char } word dup $len { $>cc } { drop 0 dictnew } cond 2 'nop } ::_ CC{ 166 | // ( x D -- ) 167 | { swap ?dup { .GR_ .+cc_ } { .cc_ } cond } : .GR+cc_ 168 | { .GR+cc_ space } : .GR+cc 169 | { -rot Gram, swap dict, } : Gram+cc, 170 | 171 | // Libraries 172 | // ( -- D ) New empty library collection 173 | ' dictnew : Libs{ 174 | // ( D -- D ) Return library collection as dictionary 175 | 'nop : }Libs 176 | // ( D c x -- D' ) Add a public/private library c to collection D 177 | { -rot B, swap ref, 189 | } cond 190 | } swap ! 191 | // b S n -- b' 192 | { swap $>B swap append-long-bytes } : append-long-string 193 | // S -- c 194 | { 195 | } : simple-transfer-body 196 | 197 | // ( S -- x ) parse public key 198 | { dup $len 48 <> abort"public key must be 48 characters long" 199 | base64url>B dup Blen 36 <> abort"public key must be 48 characters long" 200 | 34 B| 16 B>u@ over crc16 <> abort"crc16 mismatch in public key" 201 | 16 B>u@+ 0x3ee6 <> abort"invalid tag in public key" 202 | 256 B>u@ 203 | } : parse-pubkey 204 | { bl word parse-pubkey 1 'nop } ::_ PK' 205 | // ( x -- S ) serialize public key 206 | { 256 u>B B{3ee6} swap B+ dup crc16 16 u>B B+ B>base64 } : pubkey>$ 207 | { pubkey>$ type } : .pubkey 208 | 209 | // ( S -- x ) parse validator-encoded public key 210 | { base64>B dup Blen 36 <> abort"public key with magic must be 36 bytes long" 211 | 4 B| swap 32 B>u@ 0xC6B41348 <> abort"unknown magic for public key (not Ed25519)" 212 | } : parse-val-pubkey 213 | { bl word parse-val-pubkey 1 'nop } ::_ VPK' 214 | { char } word base64>B 1 'nop } ::_ B64{ 215 | 216 | // adnl address parser 217 | { 256 u>B B{2D} swap B+ dup crc16 16 u>B B+ } : adnl-preconv 218 | { swap 32 /mod dup 26 < { 65 } { 24 } cond + rot swap hold } : Base32# 219 | { <# ' Base32# 8 times #> } : Base32#*8 220 | { "" over Blen 5 / { swap 40 B>u@+ Base32#*8 nip rot swap $+ } swap times nip } : B>Base32 221 | 222 | // ( x -- S ) Converts an adnl-address from a 256-bit integer to a string 223 | { adnl-preconv B>Base32 1 $| nip } : adnl>$ 224 | 225 | { 65 - dup 0>= { -33 and dup 26 < } { 41 + dup 25 > over 32 < and } cond ?dup nip } : Base32-digit? 226 | { Base32-digit? not abort"not a Base32 digit" } : Base32-digit 227 | { 0 { over $len } { swap 1 $| -rot (char) Base32-digit swap 5 << + } while nip } : Base32-number 228 | { B{} { over $len } { swap 8 $| -rot Base32-number 40 u>B B+ } while nip } : Base32>B 229 | 230 | // ( S -- x ) Converts an adnl address from a string to 256-bit integer 231 | { dup $len 55 <> abort"not 55 alphanumeric characters" "F" swap $+ Base32>B 232 | 33 B| 16 B>u@ over crc16 <> abort"crc16 checksum mismatch" 233 | 8 B>u@+ 0x2D <> abort"not a valid adnl address" 256 B>u@ } : $>adnl 234 | 235 | { 65 - dup 0>= { -33 and 10 + dup 16 < } { 17 + dup 0>= over 10 < and } cond ?dup nip } : hex-digit? 236 | // ( S -- x -1 or 0 ) Parses a hexadecimal integer 237 | { dup $len { 238 | 0 { 239 | 4 << swap 1 $| -rot (char) hex-digit? // S a d -1 or S a 0 240 | { + over $len 0= } { drop -1 true } cond 241 | } until 242 | dup 0< { 2drop false } { nip true } cond 243 | } { drop false } cond 244 | } : hex$>u? 245 | // ( S -- x ) 246 | { hex$>u? not abort"not a hexadecimal number" } : hex$>u 247 | 248 | { dup $len 64 = { hex$>u } { 249 | dup $len 55 = { $>adnl } { 250 | true abort"invalid adnl address" 251 | } cond } cond 252 | } : parse-adnl-addr 253 | { adnl>$ type } : .adnl 254 | { bl word parse-adnl-addr 1 'nop } ::_ adnl: 255 | 256 | // ( x a b -- a<=x<=b ) 257 | { 2 pick >= -rot >= and } : in-range? 258 | 259 | // ( c i -- ? ) Checks whether c is a valid value for config param #i 260 | def? config-valid? { 261 | { nip 0>= { ."warning: cannot check validity of configuration parameter value, use create-state instead of fift to check validity" cr } if 262 | true } : config-valid? 263 | } ifnot 264 | 265 | { dup -1000 = { drop 277 | { 278 | // anycast_info$_ depth:(#<= 30) { depth >= 1 } 279 | // rewrite_pfx:(bits depth) = Anycast; 280 | 30 u@+ swap // get depth 281 | 282 | dup 1 > { 283 | dup 2 roll swap u@+ // get rewrite_pfx 284 | // return depth, rewrite_pfx, slice 285 | } 286 | { 287 | drop // drop depth (<=1) 288 | 0 0 2 roll // set anycast to none 289 | } cond 290 | } 291 | { 292 | 0 0 2 roll // set anycast to none 293 | } cond 294 | } : maybe-anycast 295 | 296 | // Rewrite first bits of addr with anycast info 297 | { // input: anycast depth, rewrite_pfx, workchain, slice, address length 298 | 4 -roll 299 | 3 roll dup dup 0 = { 2drop 2 roll drop } 300 | { 301 | rot swap u@+ swap drop 302 | 3 roll 303 | // Get addr: addr_none$00 / addr_extern$01 / addr_std$10 / addr_var$11 316 | { // if greater that zero 317 | dup 1 > 318 | { 319 | 2 = 320 | { 321 | // if addr_std$10 322 | // anycast:(Maybe Anycast) 323 | // workchain_id:int8 324 | // address:bits256 = MsgAddressInt; 325 | maybe-anycast // get anycast depth, bits, slice 326 | 8 i@+ // get workchain 327 | 256 parse-address-with-anycast 328 | `addr-std swap 329 | } 330 | 331 | { 332 | // if addr_var$11 333 | // anycast:(Maybe Anycast) 334 | // addr_len:(## 9) 335 | // workchain_id:int32 336 | // address:(bits addr_len) = MsgAddressInt; 337 | maybe-anycast // get anycast depth, bits, slice 338 | 9 u@+ // get addr_len 339 | 32 i@+ // get workchain 340 | swap 2 -roll // move workchain to neede position 341 | swap parse-address-with-anycast 342 | `addr-var swap 343 | } cond 344 | 345 | } 346 | { 347 | drop // drop header (dup for statment upper) 348 | // if addr_extern$01 349 | // addr_extern$01 len:(## 9) 350 | // external_address:(bits len) 351 | 9 u@+ swap // bit len 352 | u@+ // external_address 353 | `addr-extern swap 354 | } cond 355 | } 356 | { 357 | swap 358 | // if addr_none$00 359 | `addr-none swap 360 | } cond 361 | } : addr@+ 362 | 363 | { addr@+ drop } : addr@ 364 | 365 | // User-friendly prints output of addr@ 366 | // (0 A or addr A or wc addr A -- ) 367 | { 368 | dup `addr-none eq? 369 | { 2drop ."addr_none" } 370 | { 371 | `addr-extern eq? 372 | { (dump) type } 373 | { (x.) swap (dump) ":" $+ swap $+ type } 374 | cond 375 | } 376 | cond 377 | } : print-addr // print addr with workchain 378 | 379 | forget maybe-anycast 380 | forget parse-address-with-anycast -------------------------------------------------------------------------------- /token-contract/ft/build/jetton-minter-ICO.fif: -------------------------------------------------------------------------------- 1 | "Asm.fif" include 2 | // automatically generated from `../stdlib.fc` `params.fc` `op-codes.fc` `jetton-utils.fc` `jetton-minter-ICO.fc` 3 | PROGRAM{ 4 | DECLPROC force_chain 5 | DECLPROC pack_jetton_wallet_data 6 | DECLPROC calculate_jetton_wallet_state_init 7 | DECLPROC calculate_jetton_wallet_address 8 | DECLPROC calculate_user_jetton_wallet_address 9 | DECLPROC load_data 10 | DECLPROC save_data 11 | DECLPROC mint_tokens 12 | DECLPROC recv_internal 13 | 106029 DECLMETHOD get_jetton_data 14 | 103289 DECLMETHOD get_wallet_address 15 | force_chain PROC:<{ 16 | // addr 17 | REWRITESTDADDR // _8 _9 18 | DROP // wc 19 | 0 PUSHINT // wc _5 20 | EQUAL // _6 21 | 333 THROWIFNOT 22 | }> 23 | pack_jetton_wallet_data PROC:<{ 24 | // balance owner_address jetton_master_address jetton_wallet_code 25 | NEWC // balance owner_address jetton_master_address jetton_wallet_code _4 26 | s0 s4 XCHG2 // jetton_wallet_code owner_address jetton_master_address _4 balance 27 | STVARUINT16 // jetton_wallet_code owner_address jetton_master_address _5 28 | ROT // jetton_wallet_code jetton_master_address _5 owner_address 29 | STSLICER // jetton_wallet_code jetton_master_address _6 30 | SWAP // jetton_wallet_code _6 jetton_master_address 31 | STSLICER // jetton_wallet_code _7 32 | STREF // _8 33 | ENDC // _9 34 | }> 35 | calculate_jetton_wallet_state_init PROC:<{ 36 | // owner_address jetton_master_address jetton_wallet_code 37 | 0 PUSHINT // owner_address jetton_master_address jetton_wallet_code _3=0 38 | s0 s1 s2 XCPUXC 39 | s4 s0 s3 XC2PU // _3=0 jetton_wallet_code _4=0 owner_address jetton_master_address jetton_wallet_code 40 | pack_jetton_wallet_data INLINECALLDICT // _3=0 jetton_wallet_code _5 41 | s2 PUSH // _3=0 jetton_wallet_code _5 _6=0 42 | NEWC // _3=0 jetton_wallet_code _5 _6=0 _7 43 | 2 STU // _3=0 jetton_wallet_code _5 _9 44 | s1 s2 XCHG // _3=0 _5 jetton_wallet_code _9 45 | STDICT // _3=0 _5 _10 46 | STDICT // _3=0 _11 47 | 1 STU // _13 48 | ENDC // _14 49 | }> 50 | calculate_jetton_wallet_address PROC:<{ 51 | // state_init 52 | HASHCU // _1 53 | 0 PUSHINT // _1 _2 54 | 4 PUSHINT // _1 _2 _3=4 55 | NEWC // _1 _2 _3=4 _4 56 | 3 STU // _1 _2 _6 57 | 8 STI // _1 _8 58 | 256 STU // _10 59 | ENDC // _11 60 | CTOS // _12 61 | }> 62 | calculate_user_jetton_wallet_address PROC:<{ 63 | // owner_address jetton_master_address jetton_wallet_code 64 | calculate_jetton_wallet_state_init INLINECALLDICT // _3 65 | calculate_jetton_wallet_address INLINECALLDICT // _4 66 | }> 67 | load_data PROC:<{ 68 | // 69 | c4 PUSH // _1 70 | CTOS // ds 71 | LDVARUINT16 // _3 ds 72 | LDMSGADDR // _3 _5 ds 73 | LDREF // _3 _5 _7 ds 74 | LDREF // _3 _5 _7 _18 _17 75 | DROP // _3 _5 _7 _9 76 | }> 77 | save_data PROC:<{ 78 | // total_supply admin_address content jetton_wallet_code 79 | NEWC // total_supply admin_address content jetton_wallet_code _4 80 | s0 s4 XCHG2 // jetton_wallet_code admin_address content _4 total_supply 81 | STVARUINT16 // jetton_wallet_code admin_address content _5 82 | ROT // jetton_wallet_code content _5 admin_address 83 | STSLICER // jetton_wallet_code content _6 84 | STREF // jetton_wallet_code _7 85 | STREF // _8 86 | ENDC // _9 87 | c4 POP 88 | }> 89 | mint_tokens PROC:<{ 90 | // to_address jetton_wallet_code amount master_msg 91 | MYADDR // to_address jetton_wallet_code amount master_msg _5 92 | s4 s0 s3 XCHG3 // amount master_msg to_address _5 jetton_wallet_code 93 | calculate_jetton_wallet_state_init INLINECALLDICT // amount master_msg state_init 94 | DUP // amount master_msg state_init state_init 95 | calculate_jetton_wallet_address INLINECALLDICT // amount master_msg state_init to_wallet_address 96 | 7 PUSHINT // amount master_msg state_init to_wallet_address _14 97 | 24 PUSHINT // amount master_msg state_init to_wallet_address _14 _15=24 98 | NEWC // amount master_msg state_init to_wallet_address _14 _15=24 _16 99 | 6 STU // amount master_msg state_init to_wallet_address _14 _18 100 | ROT // amount master_msg state_init _14 _18 to_wallet_address 101 | STSLICER // amount master_msg state_init _14 _19 102 | s0 s4 XCHG2 // _14 master_msg state_init _19 amount 103 | STVARUINT16 // _14 master_msg state_init _20 104 | s1 s3 XCHG // state_init master_msg _14 _20 105 | 108 STU // state_init master_msg _36 106 | s1 s2 XCHG // master_msg state_init _36 107 | STREF // master_msg _37 108 | STREF // msg 109 | ENDC // _39 110 | 1 PUSHINT // _39 _40=1 111 | SENDRAWMSG 112 | }> 113 | recv_internal PROC:<{ 114 | // msg_value in_msg_full in_msg_body 115 | SWAP // msg_value in_msg_body in_msg_full 116 | CTOS // msg_value in_msg_body cs 117 | 4 LDU // msg_value in_msg_body flags cs 118 | SWAP 119 | 1 PUSHINT // msg_value in_msg_body cs flags _9=1 120 | AND // msg_value in_msg_body cs _10 121 | IFJMP:<{ // msg_value in_msg_body cs 122 | 3 BLKDROP // 123 | }> // msg_value in_msg_body cs 124 | LDMSGADDR // msg_value in_msg_body _145 _144 125 | DROP // msg_value in_msg_body sender_address 126 | load_data INLINECALLDICT // msg_value in_msg_body sender_address total_supply admin_address content jetton_wallet_code 127 | s5 PUSH // msg_value in_msg_body sender_address total_supply admin_address content jetton_wallet_code in_msg_body 128 | SEMPTY // msg_value in_msg_body sender_address total_supply admin_address content jetton_wallet_code _19 129 | IFJMP:<{ // msg_value in_msg_body sender_address total_supply admin_address content jetton_wallet_code 130 | s5 POP // msg_value jetton_wallet_code sender_address total_supply admin_address content 131 | 10000000 PUSHINT // msg_value jetton_wallet_code sender_address total_supply admin_address content amount=10000000 132 | s6 s6 XCPU // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content msg_value amount=10000000 133 | SUB // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content buy_amount 134 | DUP // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content buy_amount buy_amount 135 | 0 GTINT // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content buy_amount _26 136 | 76 THROWIFNOT // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount 137 | 0 PUSHINT // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 138 | DUP // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _31=0 139 | 0x178d4519 PUSHINT // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _31=0 _32 140 | NEWC // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _31=0 _32 _33 141 | 32 STU // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _31=0 _35 142 | 64 STU // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _37 143 | s2 PUSH // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _37 jetton_amount 144 | STVARUINT16 // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _38 145 | MYADDR // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _38 _39 146 | STSLICER // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _40 147 | s6 PUSH // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _40 sender_address 148 | STSLICER // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _41 149 | OVER // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _41 _42=0 150 | STVARUINT16 // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _30=0 _43 151 | 1 STU // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount _45 152 | ENDC // amount=10000000 jetton_wallet_code sender_address total_supply admin_address content jetton_amount master_msg 153 | s6 PUSH 154 | s3 s6 XCHG 155 | s0 s8 s8 XCHG3 // jetton_amount jetton_wallet_code content total_supply admin_address sender_address jetton_wallet_code amount=10000000 master_msg 156 | mint_tokens CALLDICT 157 | s0 s4 XCHG // admin_address jetton_wallet_code content total_supply jetton_amount 158 | ADD // admin_address jetton_wallet_code content _48 159 | s3 s1 s3 XCHG3 // _48 admin_address content jetton_wallet_code 160 | save_data INLINECALLDICT 161 | }> // msg_value in_msg_body sender_address total_supply admin_address content jetton_wallet_code 162 | s6 POP // jetton_wallet_code in_msg_body sender_address total_supply admin_address content 163 | s0 s4 XCHG // jetton_wallet_code content sender_address total_supply admin_address in_msg_body 164 | 32 LDU // jetton_wallet_code content sender_address total_supply admin_address op in_msg_body 165 | 64 LDU // jetton_wallet_code content sender_address total_supply admin_address op query_id in_msg_body 166 | 21 PUSHINT // jetton_wallet_code content sender_address total_supply admin_address op query_id in_msg_body _58 167 | s3 s(-1) PUXC // jetton_wallet_code content sender_address total_supply admin_address op query_id in_msg_body op _58 168 | EQUAL // jetton_wallet_code content sender_address total_supply admin_address op query_id in_msg_body _59 169 | IFJMP:<{ // jetton_wallet_code content sender_address total_supply admin_address op query_id in_msg_body 170 | 2 1 BLKDROP2 // jetton_wallet_code content sender_address total_supply admin_address in_msg_body 171 | s3 s1 XCPU // jetton_wallet_code content in_msg_body total_supply admin_address sender_address admin_address 172 | SDEQ // jetton_wallet_code content in_msg_body total_supply admin_address _61 173 | 73 THROWIFNOT 174 | s0 s2 XCHG // jetton_wallet_code content admin_address total_supply in_msg_body 175 | LDMSGADDR // jetton_wallet_code content admin_address total_supply to_address in_msg_body 176 | LDVARUINT16 // jetton_wallet_code content admin_address total_supply to_address amount in_msg_body 177 | LDREF // jetton_wallet_code content admin_address total_supply to_address amount _159 _158 178 | DROP // jetton_wallet_code content admin_address total_supply to_address amount master_msg 179 | DUP // jetton_wallet_code content admin_address total_supply to_address amount master_msg master_msg 180 | CTOS // jetton_wallet_code content admin_address total_supply to_address amount master_msg master_msg_cs 181 | 96 PUSHINT // jetton_wallet_code content admin_address total_supply to_address amount master_msg master_msg_cs _77 182 | SDSKIPFIRST // jetton_wallet_code content admin_address total_supply to_address amount master_msg master_msg_cs 183 | LDVARUINT16 // jetton_wallet_code content admin_address total_supply to_address amount master_msg _161 _160 184 | DROP // jetton_wallet_code content admin_address total_supply to_address amount master_msg jetton_amount 185 | s7 PUSH 186 | s3 s4 XCHG 187 | s4 s2 XCHG2 // jetton_wallet_code content admin_address total_supply jetton_amount to_address jetton_wallet_code amount master_msg 188 | mint_tokens CALLDICT 189 | ADD // jetton_wallet_code content admin_address _83 190 | s2 s3 XCHG2 // _83 admin_address content jetton_wallet_code 191 | save_data INLINECALLDICT 192 | }> // jetton_wallet_code content sender_address total_supply admin_address op query_id in_msg_body 193 | 0x7bdd97de PUSHINT // jetton_wallet_code content sender_address total_supply admin_address op query_id in_msg_body _85 194 | s1 s3 XCHG // jetton_wallet_code content sender_address total_supply admin_address in_msg_body query_id op _85 195 | EQUAL // jetton_wallet_code content sender_address total_supply admin_address in_msg_body query_id _86 196 | IFJMP:<{ // jetton_wallet_code content sender_address total_supply admin_address in_msg_body query_id 197 | SWAP // jetton_wallet_code content sender_address total_supply admin_address query_id in_msg_body 198 | LDVARUINT16 // jetton_wallet_code content sender_address total_supply admin_address query_id jetton_amount in_msg_body 199 | LDMSGADDR // jetton_wallet_code content sender_address total_supply admin_address query_id jetton_amount from_address in_msg_body 200 | MYADDR // jetton_wallet_code content sender_address total_supply admin_address query_id jetton_amount from_address in_msg_body _94 201 | s2 s0 s9 XC2PU // jetton_wallet_code content sender_address total_supply admin_address query_id jetton_amount in_msg_body from_address _94 jetton_wallet_code 202 | calculate_user_jetton_wallet_address INLINECALLDICT // jetton_wallet_code content sender_address total_supply admin_address query_id jetton_amount in_msg_body _95 203 | s0 s6 XCHG2 // jetton_wallet_code content in_msg_body total_supply admin_address query_id jetton_amount _95 sender_address 204 | SDEQ // jetton_wallet_code content in_msg_body total_supply admin_address query_id jetton_amount _96 205 | 74 THROWIFNOT 206 | s1 s3 XCHG // jetton_wallet_code content in_msg_body query_id admin_address total_supply jetton_amount 207 | SUB // jetton_wallet_code content in_msg_body query_id admin_address _98 208 | s0 s3 XCHG 209 | s1 s4 s5 XCHG3 // in_msg_body query_id _98 admin_address content jetton_wallet_code 210 | save_data INLINECALLDICT 211 | SWAP // query_id in_msg_body 212 | LDMSGADDR // query_id _167 _166 213 | DROP // query_id response_address 214 | DUP // query_id response_address response_address 215 | 2 PLDU // query_id response_address _104 216 | 0 NEQINT // query_id response_address _106 217 | IF:<{ // query_id response_address 218 | 0xd53276db PUSHINT // query_id response_address _108 219 | 0 PUSHINT // query_id response_address _108 _109=0 220 | 16 PUSHINT // query_id response_address _108 _109=0 _110=16 221 | NEWC // query_id response_address _108 _109=0 _110=16 _111 222 | 6 STU // query_id response_address _108 _109=0 _113 223 | s0 s3 XCHG2 // query_id _109=0 _108 _113 response_address 224 | STSLICER // query_id _109=0 _108 _114 225 | s2 PUSH // query_id _109=0 _108 _114 _115=0 226 | STVARUINT16 // query_id _109=0 _108 _116 227 | s1 s2 XCHG // query_id _108 _109=0 _116 228 | 107 STU // query_id _108 _130 229 | 32 STU // query_id _132 230 | 64 STU // msg 231 | ENDC // _135 232 | 66 PUSHINT // _135 _138 233 | SENDRAWMSG 234 | }>ELSE<{ 235 | 2DROP // 236 | }> 237 | }> // jetton_wallet_code content sender_address total_supply admin_address in_msg_body query_id 238 | 7 BLKDROP // 239 | 16 PUSHPOW2DEC // _140=65535 240 | THROWANY 241 | }> 242 | get_jetton_data PROC:<{ 243 | // 244 | load_data INLINECALLDICT // total_supply admin_address content jetton_wallet_code 245 | -1 PUSHINT // total_supply admin_address content jetton_wallet_code _5=-1 246 | 3 -ROLL // total_supply _5=-1 admin_address content jetton_wallet_code 247 | }> 248 | get_wallet_address PROC:<{ 249 | // owner_address 250 | load_data INLINECALLDICT // owner_address _8 _9 _10 _11 251 | 3 1 BLKDROP2 // owner_address jetton_wallet_code 252 | MYADDR // owner_address jetton_wallet_code _6 253 | SWAP // owner_address _6 jetton_wallet_code 254 | calculate_user_jetton_wallet_address INLINECALLDICT // _7 255 | }> 256 | }END>c 257 | --------------------------------------------------------------------------------