├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── docs ├── images │ ├── 1collection.png │ ├── 2collection.png │ ├── 3collection.png │ └── nft-metadata.jpg ├── metadata.md └── ru │ └── metadata.md ├── jest.config.js ├── package-lock.json ├── package.json ├── packages ├── contracts │ ├── deployer │ │ └── DeployerLocal.ts │ ├── nft-auction-v2 │ │ ├── NftAuctionV2.data.ts │ │ ├── NftAuctionV2.source.ts │ │ ├── NftAuctionV2.spec.ts │ │ ├── NftAuctionV2Local.ts │ │ ├── description-ru.md │ │ └── isNftAuctionV2Contract.ts │ ├── nft-auction │ │ ├── NftAuction.data.ts │ │ ├── NftAuction.source.ts │ │ ├── NftAuction.spec.ts │ │ ├── NftAuctionLocal.ts │ │ ├── isNftAuctionContract.ts │ │ └── test-tools.ts │ ├── nft-collection │ │ ├── NftCollection.data.ts │ │ ├── NftCollection.source.ts │ │ ├── NftCollection.spec.ts │ │ ├── NftCollectionLocal.ts │ │ ├── isNftCollectionContract.spec.ts │ │ └── isNftCollectionContract.ts │ ├── nft-fixprice-sale-v2 │ │ ├── NftFixpriceSaleV2.data.ts │ │ ├── NftFixpriceSaleV2.source.ts │ │ ├── NftFixpriceSaleV2.spec.ts │ │ ├── NftFixpriceSaleV2Local.ts │ │ └── isNftFixPriceSaleV2Contract.ts │ ├── nft-fixprice-sale-v3 │ │ ├── NftFixpriceSaleV3.data.ts │ │ ├── NftFixpriceSaleV3.source.ts │ │ ├── NftFixpriceSaleV3.spec.ts │ │ ├── NftFixpriceSaleV3Local.ts │ │ ├── description-ru.md │ │ └── isNftFixPriceSaleV3Contract.ts │ ├── nft-fixprice-sale-v4 │ │ └── description-ru.md │ ├── nft-fixprice-sale │ │ ├── NftFixPriceSale.data.ts │ │ ├── NftFixPriceSale.source.ts │ │ ├── NftFixpriceSale.spec.ts │ │ ├── NftFixpriceSaleLocal.ts │ │ └── isNftFixPriceSaleContract.ts │ ├── nft-item │ │ ├── NftItem.data.ts │ │ ├── NftItem.source.ts │ │ ├── NftItem.spec.ts │ │ ├── NftItemLocal.ts │ │ ├── isNftItemContract.spec.ts │ │ └── isNftItemContract.ts │ ├── nft-marketplace │ │ ├── NftMarketplace.data.ts │ │ ├── NftMarketplace.source.ts │ │ ├── NftMarketplace.spec.ts │ │ └── NftMarketplaceLocal.ts │ ├── nft-offer │ │ ├── NftOffer.source.ts │ │ ├── NftOffer.spec.ts │ │ ├── NftOfferData.ts │ │ ├── NftOfferLocal.ts │ │ ├── index-notice-ru.md │ │ └── isNftOfferContract.ts │ ├── nft-raffle │ │ ├── main.test.ts │ │ ├── raffle.local.ts │ │ ├── raffle.queries.ts │ │ ├── raffle.source.ts │ │ └── raffle.storage.ts │ ├── nft-swap │ │ ├── Swap.data.ts │ │ ├── Swap.source.ts │ │ ├── Swap.spec.ts │ │ └── SwapLocal.ts │ ├── sbt-item │ │ ├── SbtItem.data.ts │ │ ├── SbtItem.source.ts │ │ ├── SbtItem.spec.ts │ │ └── SbtItemLocal.ts │ └── sources │ │ ├── deployer │ │ ├── deployer.base64 │ │ ├── deployer.boc │ │ ├── deployer.fc │ │ └── deployer.fif │ │ ├── imports │ │ └── stdlib.fc │ │ ├── nft-auction-v2 │ │ ├── build.sh │ │ ├── nft-auction-v2-code.base64 │ │ ├── nft-auction-v2-code.boc │ │ ├── nft-auction-v2-code.fif │ │ ├── nft-auction-v2.func │ │ └── struct │ │ │ ├── exit-codes.func │ │ │ ├── get-met.func │ │ │ ├── handles.func │ │ │ ├── math.func │ │ │ ├── msg-utils.func │ │ │ ├── op-codes.func │ │ │ └── storage.func │ │ ├── nft-auction-v3r2 │ │ ├── nft-auction-v3r2-code.base64 │ │ ├── nft-auction-v3r2-code.boc │ │ ├── nft-auction-v3r2-code.fif │ │ ├── nft-auction-v3r2.func │ │ └── struct │ │ │ ├── exit-codes.func │ │ │ ├── get-met.func │ │ │ ├── handles.func │ │ │ ├── math.func │ │ │ ├── msg-utils.func │ │ │ ├── op-codes.func │ │ │ └── storage.func │ │ ├── nft-auction-v3r3 │ │ ├── nft-auction-v3r3.func │ │ └── struct │ │ │ ├── exit-codes.func │ │ │ ├── math.func │ │ │ ├── msg-utils.func │ │ │ └── op-codes.func │ │ ├── nft-auction │ │ ├── build.sh │ │ ├── nft-auction-code.base64 │ │ ├── nft-auction-code.boc │ │ ├── nft-auction-code.fif │ │ ├── nft-auction.func │ │ └── struct │ │ │ ├── exit-codes.func │ │ │ ├── get-met.func │ │ │ ├── handles.func │ │ │ ├── math.func │ │ │ ├── msg-utils.func │ │ │ ├── op-codes.func │ │ │ └── storage.func │ │ ├── nft-collection-editable-v2.fc │ │ ├── nft-collection-editable.fc │ │ ├── nft-collection.fc │ │ ├── nft-fixprice-sale-v2.fc │ │ ├── nft-fixprice-sale-v3.fc │ │ ├── nft-fixprice-sale-v3r2.fc │ │ ├── nft-fixprice-sale-v3r3.fc │ │ ├── nft-fixprice-sale-v4r1.fc │ │ ├── nft-fixprice-sale.fc │ │ ├── nft-item-editable-DRAFT.fc │ │ ├── nft-item-editable-v2.fc │ │ ├── nft-item-v2.fc │ │ ├── nft-item.fc │ │ ├── nft-marketplace-v2.fc │ │ ├── nft-marketplace.fc │ │ ├── nft-offer-v1r3.fc │ │ ├── nft-offer.fc │ │ ├── nft-raffle │ │ ├── build.sh │ │ ├── main.func │ │ └── struct │ │ │ ├── constants.func │ │ │ ├── get-methods.func │ │ │ ├── handles.func │ │ │ ├── storage.func │ │ │ └── utils.func │ │ ├── nft-sale.fc │ │ ├── nft-single.fc │ │ ├── nft-swap.fc │ │ ├── op-codes.fc │ │ ├── params.fc │ │ ├── sbt-item.fc │ │ ├── sbt-single.fc │ │ └── stdlib.fc ├── nft-content │ ├── nftContent.spec.ts │ └── nftContent.ts └── utils │ ├── combineFunc.ts │ ├── compileFunc.ts │ ├── createTempFile.ts │ ├── randomAddress.ts │ ├── randomKeyPair.ts │ └── uuid.ts ├── raffle.md ├── readme.md ├── sbt.md ├── swap.md └── tsconfig.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thanks for helping us! 11 | 12 | **Describe the bug** 13 | Please describe what went wrong and how we can fix that. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Additional context** 19 | Add any other context about the problem here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Summary** 11 | Please describe your idea in one paragraph. 12 | 13 | **Motivation** 14 | Why do you think this problem is important? 15 | 16 | **Describe the solution you'd like** 17 | Please explain how you propose to solve this issue 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea 4 | yarn-error.log 5 | build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Getgems 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 | -------------------------------------------------------------------------------- /docs/images/1collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hamaster2/nft-contracts/cee67b5bfca30d9b43d862b9e4e4a2de8b12036e/docs/images/1collection.png -------------------------------------------------------------------------------- /docs/images/2collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hamaster2/nft-contracts/cee67b5bfca30d9b43d862b9e4e4a2de8b12036e/docs/images/2collection.png -------------------------------------------------------------------------------- /docs/images/3collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hamaster2/nft-contracts/cee67b5bfca30d9b43d862b9e4e4a2de8b12036e/docs/images/3collection.png -------------------------------------------------------------------------------- /docs/images/nft-metadata.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hamaster2/nft-contracts/cee67b5bfca30d9b43d862b9e4e4a2de8b12036e/docs/images/nft-metadata.jpg -------------------------------------------------------------------------------- /docs/ru/metadata.md: -------------------------------------------------------------------------------- 1 | ## Метадата коллекции 2 | 3 | ![Collection example](../images/1collection.png "Collection example") 4 | 5 | Пример json файла метадаты коллекции 6 | 7 | ```json 8 | { 9 | "name": "Magic Mushrooms", 10 | "description": "Magic Mushrooms is an NFT collection of magic mushrooms created specially for Telegram and The Open Network.\n\nHand drawing brings the collection an artistic value, while various accessories and materials bring uniqueness and significance in our rapidly changing world.", 11 | "image": "https://s.getgems.io/nft/c/62695cb92d780b7496caea3a/avatar.png", 12 | "cover_image": "https://s.getgems.io/nft/c/62695cb92d780b7496caea3a/cover.png", 13 | "social_links": [ 14 | "https://t.me/ton_magic_mushrooms" 15 | ] 16 | } 17 | ``` 18 | 19 | | | | | 20 | |---|---|---| 21 | |[1] name|название коллекции|рекомндуемая длина не более 15-30 символов| 22 | |[2] description|описание коллекции|рекомндуемая длина до 500 символов| 23 | |[3] image|ссылка на изображение|поддерживаются https и ipfs ссылки, рекомендуется использовать квадратное изображение разметом от 400x400 пикселей до 1000x1000 пикселей, поддерживаются форматы png, jpg, gif, webp, svg, размер файла не более 30 мб. Для анимированных изображений количество кадров не более ста.| 24 | |[4] cover_image|ссылка на обложку|поддерживаются https и ipfs ссылки, рекомендуется использовать изображение размером 2880x680 пикселей, поддерживаются форматы png, jpg, gif, webp, svg, размер файла не более 30 мб. Для анимированных изображений количество кадров не более 30. Обратите внимание чтоб это изображение используется для превью коллекции, см. скриншоты| 25 | |[5] social_links|массив со ссылками на соц. сети|не более 10 ссылок| 26 | 27 | 28 | 29 | ![Mobile view|width=300px](../images/2collection.png) 30 | ![Collection preview](../images/3collection.png "Collection preview") 31 | 32 | 33 | ## Метадата NFT 34 | 35 | ```json 36 | { 37 | "name": "Magic Mushroom #57", 38 | "description": "Hand drawing brings the NFT an artistic value, while various accessories and materials bring uniqueness and significance in our rapidly changing world.", 39 | "image": "https://s.getgems.io/nft/c/62695cb92d780b7496caea3a/nft/56/629b9349e034e8e582cf6448.png", 40 | "attributes": [ 41 | { 42 | "trait_type": "Material", 43 | "value": "Wool fabric" 44 | }, 45 | { 46 | "trait_type": "Hat", 47 | "value": "Top hat" 48 | }, 49 | { 50 | "trait_type": "Glasses", 51 | "value": "None" 52 | }, 53 | { 54 | "trait_type": "Item", 55 | "value": "None" 56 | }, 57 | { 58 | "trait_type": "Background", 59 | "value": "Dark" 60 | } 61 | ] 62 | } 63 | ``` 64 | 65 | | | | | 66 | |--------------|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 67 | | name | название нфт | рекомндуемая длина не более 15-30 символов | 68 | | description | описание нфт | рекомндуемая длина до 500 символов | 69 | | image | ссылка на изображение | поддерживаются https и ipfs ссылки, рекомендуется использовать квадратное изображение разметом 1000x1000 пикселей, поддерживаются форматы png, jpg, gif, webp, svg, размер файла не более 30 мб. Если вы используете видео то рекомендуется изображением сделать первый кадр этого видео | 70 | | content_type | тип контента по ссылке из content_url | Например video/mp4 | 71 | | content_url | ссылка на дополнительный контент | На текущий момент поддерживаются только видео, mp4 webm quicktime или mpeg, максимальный размер файла 100мб, рекомендуемый зармер видео 1000x1000 пикселей | 72 | | lottie | ссылка на json файл с лотти анимацией | Если указано то на странице с нфт будет проигрываться lottie анимания из этого файла. [Прмер нфт использующих lottie](https://getgems.io/collection/EQAG2BH0JlmFkbMrLEnyn2bIITaOSssd4WdisE4BdFMkZbir/EQCoADmGFboLrgOCDSwAe-jI-lOOVoRYllA5F4WeIMokINW8) | 73 | | attributes | атрибуты нфт | Массив атрибутов где для каждлго атрибута указаны trait_type (название атрибуда) value (значение атрибута) | 74 | 75 | ![Mobile view|width=600px](../images/nft-metadata.jpg) -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nft-contracts", 3 | "version": "1.0.0", 4 | "description": "GetGems NFT contracts", 5 | "main": "index.js", 6 | "author": "Narek Abovyan ", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "@types/bn.js": "^5.1.0", 10 | "@types/chai": "^4.3.3", 11 | "@types/jest": "^28.1.4", 12 | "jest": "^28.1.2", 13 | "ts-jest": "^28.0.5", 14 | "ts-node": "^10.9.1", 15 | "typescript": "^4.7.4" 16 | }, 17 | "dependencies": { 18 | "@ton.org/func-js": "^0.1.3", 19 | "@types/uuid": "^8.3.4", 20 | "bn.js": "^5.2.1", 21 | "chai": "^4.3.6", 22 | "mocha": "^10.0.0", 23 | "ton": "^11.18.2", 24 | "ton-compiler": "^1.0.0", 25 | "ton-contract-executor": "^0.5.2", 26 | "uuid": "^8.3.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/contracts/deployer/DeployerLocal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | Builder, 4 | Cell, 5 | CellMessage, 6 | CommonMessageInfo, 7 | contractAddress, 8 | InternalMessage, 9 | StateInit, 10 | } from 'ton' 11 | import BN from 'bn.js' 12 | 13 | export type DeployerState = {ownerAddress:Address, randomId?: number} 14 | 15 | export class DeployerLocal { 16 | static code = 'te6cckEBBQEA8AABFP8A9KQT9LzyyAsBAaDTIMcAkl8E4AHQ0wMBcbCSXwTg+kAwAdMfghAFE42RUiC64wIzMyLAAZJfA+ACgQIruo4W7UTQ+kAwWMcF8uGT1DDQ0wfUMAH7AOBbhA/y8AIC/DHTP/pA0x+CCP4O3hK98tGU1NTRIfkAcMjKB8v/ydB3dIAYyMsFywIizxaCCTEtAPoCy2sTzMzJcfsAcCB0ghBfzD0UIoAYyMsFUAnPFiP6AhjLahfLHxXLPxXLAgHPFgHPFsoAIfoCygDJWaEggggPQkC5lIED6KDjDXD7AgMEAAwwgggPQkAACIMG+wAl44cc' 17 | // static testnetAddress = 'EQDDX5eqHCNW1pSm2EGmX0BvJqYnKOmDTEGuxFbwW7VM3Pi8' old 9 may 18 | static testnetAddress = 'EQDZwUjVjK__PvChXCvtCMshBT1hrPKMwzRhyTAtonUbL2M3' 19 | // static mainnetAddress = 'EQCjc483caXMwWw2kwl2afFquAPO0LX1VyZbnZUs3toMYkk9' old 9 may 20 | static mainnetAddress = 'EQAIFunALREOeQ99syMbO6sSzM_Fa1RsPD5TBoS0qVeKQ-AR' 21 | static OP_CODE_DO_SALE = 0x0fe0ede 22 | static OP_CODE_ACCEPT_DEPLOY = 1 23 | static OP_CODE_PROXY_MESSAGE = 555 24 | static proxyMessage(msg: InternalMessage, mode: number) { 25 | return new Builder().storeUint(DeployerLocal.OP_CODE_PROXY_MESSAGE, 32) 26 | .storeRef(new Builder().storeUint(mode, 8).storeRef((() => { 27 | const c = new Cell() 28 | msg.writeTo(c) 29 | return c 30 | })()).endCell()).endCell() 31 | } 32 | 33 | static proxyMessageFromMarketplace(msg: InternalMessage, mode: number, fwdTon: BN, tonNetwork:'testnet'|'mainnet') { 34 | const deployerMessage = DeployerLocal.proxyMessage(msg, mode) 35 | return new InternalMessage({ 36 | from: null, 37 | to: DeployerLocal.getDeployerContractAddress(tonNetwork), 38 | value: fwdTon, 39 | bounce: true, 40 | body: new CommonMessageInfo({ 41 | body: new CellMessage(deployerMessage) 42 | }) 43 | }) 44 | } 45 | 46 | static buildStateCell(opts: DeployerState): Cell { 47 | const b = new Builder().storeAddress(opts.ownerAddress) 48 | if (opts.randomId) { 49 | b.storeUint(opts.randomId, 32) 50 | } 51 | return b.endCell() 52 | } 53 | 54 | static buildStateInit(source: DeployerState|Cell, code?:Cell) { 55 | const stateInit = new StateInit({ 56 | code: code ?? Cell.fromBoc(Buffer.from(DeployerLocal.code,'base64'))[0], 57 | data: source instanceof Cell ? source : DeployerLocal.buildStateCell(source), 58 | }) 59 | const address = contractAddress({ workchain: 0, initialCode: stateInit.code!, initialData: stateInit.data! }) 60 | 61 | return { 62 | address, 63 | stateInit, 64 | } 65 | } 66 | 67 | static createDeployMessage() { 68 | const b = new Builder() 69 | b.storeUint(DeployerLocal.OP_CODE_ACCEPT_DEPLOY, 32) 70 | return b.endCell() 71 | } 72 | 73 | static createSaleDeployPayload(saleContractStateInit: StateInit, saleContractDeployMessage: Cell) { 74 | const b = new Builder() 75 | b.storeUint(DeployerLocal.OP_CODE_DO_SALE, 32) 76 | .storeRef((() => { 77 | const c = new Cell() 78 | saleContractStateInit.writeTo(c) 79 | return c 80 | })()) 81 | .storeRef(saleContractDeployMessage) 82 | return b.endCell() 83 | } 84 | 85 | static deployForProduction(ownerAddress: Address) { 86 | return DeployerLocal.buildStateInit({ 87 | ownerAddress, 88 | }) 89 | } 90 | 91 | static getDeployerContractAddress(tonNetwork:'testnet'|'mainnet') { 92 | let address: Address 93 | if (tonNetwork === 'testnet') { 94 | address = Address.parse(DeployerLocal.testnetAddress) 95 | } else if (tonNetwork === 'mainnet') { 96 | address = Address.parse(DeployerLocal.mainnetAddress) 97 | } else { 98 | ((x:never) => { 99 | throw new Error(`Unexpected network ${x}`) 100 | })(tonNetwork) 101 | } 102 | return address 103 | } 104 | 105 | static isDeployerAddress(addr: Address) { 106 | const currentMainnet = DeployerLocal.getDeployerContractAddress('mainnet') 107 | if (currentMainnet.equals(addr)) { 108 | return true 109 | } 110 | 111 | const currentTestnet = DeployerLocal.getDeployerContractAddress('testnet') 112 | if (currentTestnet.equals(addr)) { 113 | return true 114 | } 115 | 116 | const oldTestnetAddress = 'EQDDX5eqHCNW1pSm2EGmX0BvJqYnKOmDTEGuxFbwW7VM3Pi8' 117 | const oldMainnetAddress = 'EQCjc483caXMwWw2kwl2afFquAPO0LX1VyZbnZUs3toMYkk9' 118 | 119 | const f = addr.toFriendly() 120 | return f === oldMainnetAddress || f === oldTestnetAddress 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /packages/contracts/nft-auction-v2/NftAuctionV2Local.ts: -------------------------------------------------------------------------------- 1 | import {SmartContract} from "ton-contract-executor"; 2 | import {Address, Cell, CellMessage, CommonMessageInfo, contractAddress, InternalMessage, toNano} from "ton"; 3 | import {buildNftAuctionV2DataCell, NftAuctionV2Data, Queries} from "./NftAuctionV2.data"; 4 | import {NftAuctionV2CodeCell} from "./NftAuctionV2.source"; 5 | import {NftAuctionLocal} from "../nft-auction/NftAuctionLocal"; 6 | import BN from "bn.js"; 7 | 8 | export class NftAuctionV2Local extends NftAuctionLocal { 9 | 10 | public static queries = Queries 11 | public queries = Queries 12 | 13 | async sendStopMessage(from:Address) { 14 | const msg = this.queries.stopMessage(); 15 | return await this.contract.sendInternalMessage(new InternalMessage({ 16 | to: this.address, 17 | from: from, 18 | value: toNano('1'), 19 | bounce: true, 20 | body: new CommonMessageInfo({ 21 | body: new CellMessage(msg) 22 | }) 23 | })); 24 | } 25 | 26 | async sendCancelMessage(from:Address, currentBalance?: BN) { 27 | const msg = this.queries.cancelMessage(); 28 | return await this.contract.sendInternalMessage(new InternalMessage({ 29 | to: this.address, 30 | from: from, 31 | value: currentBalance ? currentBalance.add(toNano('1')) : toNano('1'), 32 | bounce: true, 33 | body: new CommonMessageInfo({ 34 | body: new CellMessage(msg) 35 | }) 36 | })); 37 | } 38 | 39 | static async createFromConfig(config: NftAuctionV2Data) { 40 | 41 | let data = buildNftAuctionV2DataCell(config) 42 | // const code = await buildAuctionV2ContractCode(); 43 | // let contract = await SmartContract.fromCell(code, data) 44 | let contract = await SmartContract.fromCell(NftAuctionV2CodeCell, data) 45 | 46 | let address = contractAddress({ 47 | workchain: 0, 48 | initialData: contract.dataCell, 49 | initialCode: contract.codeCell 50 | }) 51 | 52 | contract.setC7Config({ 53 | myself: address 54 | }) 55 | 56 | return new NftAuctionV2Local(contract, address) 57 | } 58 | 59 | static async create(config: { code: Cell, data: Cell, address: Address }) { 60 | let contract = await SmartContract.fromCell(config.code, config.data) 61 | contract.setC7Config({ 62 | myself: config.address 63 | }) 64 | return new NftAuctionV2Local(contract, config.address) 65 | } 66 | 67 | static async createFromContract(contract: SmartContract, address: Address) { 68 | contract.setC7Config({ 69 | myself: address 70 | }) 71 | return new NftAuctionV2Local(contract, address) 72 | } 73 | } -------------------------------------------------------------------------------- /packages/contracts/nft-auction-v2/description-ru.md: -------------------------------------------------------------------------------- 1 | 2 | # Контракт аукциона 3 | 4 | Этот контракт рекомендуется использовать когда есть много покупателей на одну нфт. Продавец выставляет нфт на аукцион, 5 | а покупатели предлагают свою цену, побеждает покупатель с самой высокой ценой. Каждая последующая должна быть больше предыдущей на 6 | указанный процент (min_step) но не меньше чем на 0.1 тон. 7 | Продавец может ограничить минимальную цену, также продавец может указать максимальную цену, за которую можно сразу выкупить 8 | нфт с аукциона. Аукцион ограничен по времени рекомендуемая длительность - 3 дня, максимум 19 дней. 9 | Продавец может отменить аукцион если в нем не было ставок, если есть хотя бы одна ставка то 10 | аукцион будет идти до конца, либо до достижения максимальной ставки. Аукцион имеет защиту от ставки в последний момент, 11 | если ставка делается за 5 минут (этот параметр настраивается, но не более 1 дня) до окончания аукциона, то время аукциона продлевается еще на 5 минут. 12 | Все сообщения на этот контракт должны быть отправлены с баунс флагом, контракт выбрасывает ошибки. 13 | 14 | ### get_auction_data 15 | 16 | 1. int activated? -1 аукцион получил нфт и готов к работе 17 | 2. int end? -1 - аукцион завершен 0 - не завершен 18 | 3. int end_time timestamp времени окончания аукциона или время фактического завершения аукциона если нфт была продана 19 | 4. slice(MsgAddress) mp_addr адрес контракта маркетплейса, этот адрес может отменить аукцион без ставок или завершить его после end_time 20 | 5. slice(MsgAddress) nft_addr адрес нфт 21 | 6. slice(MsgAddress) nft_owner адрес владельца нфт, этот получит тоны с продажи нфт, может отменить аукцион без ставок или завершить его после end_time 22 | 7. int(coins) last_bid сумма последней ставки или 0 если ставки не было 23 | 8. slice(MsgAddress) last_member адрес кошелька с которого была сделана последня ставка 24 | 9. int min_step процент шага ставки 1 - 100 25 | 10. int mp_fee_addr адрес кошелька для комиссии маркетплейса 26 | 11. int mp_fee_factor процент комиссии маркетплейса выражен двумя цифрами, пример: mp_fee_factor = 10 mp_fee_base = 100, означает что комиссия маркетплейса 10% 10/100=0.1=10% 27 | 12. int mp_fee_base 28 | 13. slice(MsgAddress) royalty_fee_addr адрес кошелька для роялти коллекции 29 | 14. int royalty_fee_factor процент роялти коллекции, смотри mp_fee_factor 30 | 15. int royalty_fee_base 31 | 16. int(coins) max_bid сумма максимальной ставки или 0, если ее нет 32 | 17. int(coins) min_bid сумма минимальной ставки 33 | 18. int created_at? timestamp времени создания аукциона, используется для генерации разных адресов контрактов 34 | 19. int last_bid_at timestamp времени последней ставки или 0 если ставок не было 35 | 20. int is_canceled -1 -- означает что аукцион был отменен 36 | 21. int step_time кол-во секунд на сколько продлевается аукцион если ставка сделана в последний момент 37 | 22. int last_query_id последний обработанный query_id, если он был в запросе 38 | 39 | ### Рекомендуемые проверки перед использованием контракта 40 | 41 | - хеш кода контракта должен совпадать с эталонным, нельзя полагаться только на гет методы 42 | - get_auction_data должен вызываться нормально, без ошибок, activated? == -1 end? = 0 43 | - процент комиссии маркетплейса и процент роялти коллекции в сумме должны быть меньше 100 44 | - необходимо проверить что нфт, на которую ссылается контракт продажи (nft_address), действительно принадлежит ему, то-есть у нфт owner_address это адрес контракта продажи 45 | - значение min_step в диапазоне от 1 до 100 46 | - значение end_time не больше текущей даты +14 дней 47 | -------------------------------------------------------------------------------- /packages/contracts/nft-auction-v2/isNftAuctionV2Contract.ts: -------------------------------------------------------------------------------- 1 | import {NftAuctionV2CodeCell} from "./NftAuctionV2.source"; 2 | import {Address, Cell} from "ton"; 3 | import {SmartContract} from "ton-contract-executor"; 4 | 5 | const NftAuctionV2CodeHash = NftAuctionV2CodeCell.hash(); 6 | 7 | export async function isNftAuctionV2Contract(address: Address, codeCell: Cell, dataCell: Cell) { 8 | return NftAuctionV2CodeHash.equals(codeCell.hash()); 9 | } 10 | 11 | export async function isNftAuctionV2ContractModern(contract: SmartContract) { 12 | return NftAuctionV2CodeHash.equals(contract.codeCell.hash()); 13 | } 14 | -------------------------------------------------------------------------------- /packages/contracts/nft-auction/NftAuction.data.ts: -------------------------------------------------------------------------------- 1 | import {Address, Builder, Cell, contractAddress, StateInit} from "ton"; 2 | import BN from "bn.js"; 3 | import {NftAuctionCodeCell} from "./NftAuction.source"; 4 | import {ContractSource} from "ton/dist/contracts/sources/ContractSource"; 5 | 6 | export type NftAuctionData = { 7 | marketplaceFeeAddress: Address, 8 | marketplaceFeeFactor: BN, 9 | marketplaceFeeBase: BN, 10 | 11 | 12 | royaltyAddress: Address, 13 | royaltyFactor: BN, 14 | royaltyBase: BN, 15 | 16 | 17 | minBid: BN, 18 | maxBid: BN, 19 | minStep: BN, 20 | endTimestamp: number, 21 | createdAtTimestamp: number, 22 | 23 | stepTimeSeconds: number, 24 | tryStepTimeSeconds: number, 25 | 26 | nftOwnerAddress: Address | null, 27 | nftAddress: Address, 28 | 29 | end: boolean, 30 | marketplaceAddress: Address, 31 | activated: boolean, 32 | 33 | } 34 | 35 | export function buildNftAuctionDataCell(data: NftAuctionData) { 36 | 37 | const feesCell = new Builder() 38 | feesCell.storeAddress(data.marketplaceFeeAddress) // mp_fee_addr 39 | feesCell.storeUint(data.marketplaceFeeFactor, 32) // mp_fee_factor 40 | feesCell.storeUint(data.marketplaceFeeBase, 32) // mp_fee_base 41 | feesCell.storeAddress(data.royaltyAddress) // royalty_fee_addr 42 | feesCell.storeUint(data.royaltyFactor, 32) // royalty_fee_factor 43 | feesCell.storeUint(data.royaltyBase, 32) // royalty_fee_base 44 | 45 | 46 | const bidsCell = new Builder() 47 | bidsCell.storeCoins(data.minBid) // min_bid 48 | bidsCell.storeCoins(data.maxBid) // max_bid 49 | bidsCell.storeCoins(data.minStep) // min_step 50 | bidsCell.storeBitArray([0, 0]) // last_member 51 | bidsCell.storeCoins(0) // last_bid 52 | bidsCell.storeUint(0, 32) // last_bid_at 53 | bidsCell.storeUint(data.endTimestamp, 32) // end_time 54 | bidsCell.storeUint(data.stepTimeSeconds, 32) // step_time 55 | bidsCell.storeUint(data.tryStepTimeSeconds, 32) // try_step_time 56 | 57 | 58 | let nftCell = new Builder(); 59 | if (data.nftOwnerAddress) { 60 | nftCell.storeAddress(data.nftOwnerAddress) 61 | } else { 62 | nftCell.storeBitArray([0, 0]) 63 | } 64 | nftCell.storeAddress(data.nftAddress) // nft_addr 65 | 66 | 67 | const storage = new Builder() 68 | storage.storeBit(data.end) // end? 69 | storage.storeAddress(data.marketplaceAddress) // mp_addr 70 | storage.storeBit(data.activated) // activated 71 | storage.storeUint(data.createdAtTimestamp, 32) 72 | storage.storeBit(false) // is_canceled 73 | storage.storeRef(feesCell.endCell()) 74 | storage.storeRef(bidsCell.endCell()) 75 | storage.storeRef(nftCell.endCell()) 76 | 77 | return storage.endCell() 78 | } 79 | 80 | export function buildNftAuctionStateInit(data: NftAuctionData) { 81 | let dataCell = buildNftAuctionDataCell({ 82 | ...data, 83 | }) 84 | 85 | let stateInit = new StateInit({ 86 | code: NftAuctionCodeCell, 87 | data: dataCell 88 | }) 89 | let address = contractAddress({workchain: 0, initialCode: NftAuctionCodeCell, initialData: dataCell}) 90 | 91 | return { 92 | address, 93 | stateInit 94 | } 95 | } 96 | 97 | 98 | 99 | export const Queries = { 100 | getStateInitContract: (address:Address, dataCellBase64:string) => { 101 | const dataCellBuffer = Buffer.from(dataCellBase64, 'base64'); 102 | const dataCell = Cell.fromBoc(dataCellBuffer)[0]; 103 | return { 104 | address: address, 105 | source: { 106 | initialCode: NftAuctionCodeCell, 107 | initialData: dataCell, 108 | workchain: 0, 109 | type: 'nft_auction', 110 | backup: () => "", 111 | describe: () => "nft_auction", 112 | }, 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /packages/contracts/nft-auction/NftAuction.source.ts: -------------------------------------------------------------------------------- 1 | import {combineFunc} from "../../utils/combineFunc"; 2 | import {Cell} from "ton"; 3 | 4 | export const NftAuctionSource = () => combineFunc(__dirname, [ 5 | '../sources/nft-auction/nft-auction.func', 6 | ]) 7 | 8 | export const NftAuctionCodeBoc = 'te6cckECLgEABqIAART/APSkE/S88sgLAQIBIAIDAgFIBAUCKPIw2zyBA+74RMD/8vL4AH/4ZNs8LBkCAs4GBwIBIBwdAgEgCAkCASAaGwT1DPQ0wMBcbDyQPpAMNs8+ENSEMcF+EKwwP+Oz1vTHyHAAI0EnJlcGVhdF9lbmRfYXVjdGlvboFIgxwWwjoNb2zzgAcAAjQRZW1lcmdlbmN5X21lc3NhZ2WBSIMcFsJrUMNDTB9QwAfsA4DDg+FdSEMcFjoQxAds84PgjgLBEKCwATIIQO5rKAAGphIAFcMYED6fhW10nCAvLygQPqAdMfghAFE42REroS8vSAQNch+kAw+HZw+GJ/+GTbPBkESPhTvo8GbCHbPNs84PhCwP+OhGwh2zzg+FZSEMcF+ENSIMcFsRcRFwwEeI+4MYED6wLTHwHDABPy8otmNhbmNlbIUiDHBY6DIds83otHN0b3CBLHBfhWUiDHBbCPBNs82zyRMOLgMg0XEQ4B9oED7ItmNhbmNlbIEscFs/Ly+FHCAI5FcCCAGMjLBfhQzxb4UfoCy2rLH40KVlvdXIgYmlkIGhhcyBiZWVuIG91dGJpZCBieSBhbm90aGVyIHVzZXIugzxbJcvsA3nAg+CWCEF/MPRTIyx/LP/hWzxb4Vs8WywAh+gLLAA8BBNs8EAFMyXGAGMjLBfhXzxZw+gLLasyCCA9CQHD7AsmDBvsAf/hif/hm2zwZBPSBA+34QsD/8vL4U/gjuY8FMNs82zzg+E7CAPhOUiC+sI7V+FGORXAggBjIywX4UM8W+FH6Astqyx+NClZb3VyIGJpZCBoYXMgYmVlbiBvdXRiaWQgYnkgYW5vdGhlciB1c2VyLoM8WyXL7AN4B+HD4cfgj+HLbPOD4UxcRERICkvhRwACOPHAg+CWCEF/MPRTIyx/LP/hWzxb4Vs8WywAh+gLLAMlxgBjIywX4V88WcPoCy2rMgggPQkBw+wLJgwb7AOMOf/hi2zwTGQP8+FWh+CO5l/hT+FSg+HPe+FGOlIED6PhNUiC58vL4cfhw+CP4cts84fhR+E+gUhC5joMw2zzgcCCAGMjLBfhQzxb4UfoCy2rLH40KVlvdXIgYmlkIGhhcyBiZWVuIG91dGJpZCBieSBhbm90aGVyIHVzZXIugzxbJcvsAAfhwGRcYA/hwIPglghBfzD0UyMsfyz/4UM8W+FbPFssAggnJw4D6AssAyXGAGMjLBfhXzxaCEDuaygD6AstqzMly+wD4UfhI+EnwAyDCAJEw4w34UfhL+EzwAyDCAJEw4w2CCA9CQHD7AnAggBjIywX4Vs8WIfoCy2rLH4nPFsmDBvsAFBUWAHhwIIAYyMsF+EfPFlAD+gISy2rLH40H01hcmtldHBsYWNlIGNvbW1pc3Npb24gd2l0aGRyYXeDPFslz+wAAcHAggBjIywX4Ss8WUAP6AhLLassfjQbUm95YWx0eSBjb21taXNzaW9uIHdpdGhkcmF3gzxbJc/sAAC5QcmV2aW91cyBvd25lciB3aXRoZHJhdwCIcCCAGMjLBVADzxYh+gISy2rLH40J1lvdXIgdHJhbnNhY3Rpb24gaGFzIG5vdCBiZWVuIGFjY2VwdGVkLoM8WyYBA+wABEPhx+CP4cts8GQDQ+Ez4S/hJ+EjI+EfPFssfyx/4Ss8Wyx/LH/hV+FT4U/hSyPhN+gL4TvoC+E/6AvhQzxb4UfoCyx/LH8sfyx/I+FbPFvhXzxbJAckCyfhG+EX4RPhCyMoA+EPPFsoAyh/KAMwSzMzJ7VQAESCEDuaygCphIAANFnwAgHwAYAIBIB4fAgEgJCUCAWYgIQElupFds8+FbXScEDknAg4PhW+kSCwBEa8u7Z58KH0iQCwCASAiIwEYqrLbPPhI+En4S/hMLAFeqCzbPIIIQVVD+EL4U/hD+Ff4VvhR+FD4T/hH+Ej4SfhK+Ev4TPhO+E34RfhS+EYsAgEgJicCAW4qKwEdt++7Z58JvwnfCf8KPwpwLAIBICgpARGwybbPPhK+kSAsARGxlvbPPhH+kSAsARGvK22efCH9IkAsASWsre2efCvrpOCByTgQcHwr/SJALAH2+EFu3e1E0NIAAfhi+kAB+GPSAAH4ZNIfAfhl0gAB+GbUAdD6QAH4Z9MfAfho0x8B+Gn6QAH4atMfAfhr0x8w+GzUAdD6AAH4bfoAAfhu+gAB+G/6QAH4cPoAAfhx0x8B+HLTHwH4c9MfAfh00x8w+HXUMND6QAH4dvpALQAMMPh3f/hhRQVNYw==' 9 | 10 | export const NftAuctionCodeCell = Cell.fromBoc(Buffer.from(NftAuctionCodeBoc, 'base64'))[0] -------------------------------------------------------------------------------- /packages/contracts/nft-auction/NftAuctionLocal.ts: -------------------------------------------------------------------------------- 1 | import {SmartContract} from "ton-contract-executor"; 2 | import {Address, Cell, contractAddress, Slice} from "ton"; 3 | import BN from "bn.js"; 4 | import {buildNftAuctionDataCell, NftAuctionData} from "./NftAuction.data"; 5 | import {NftAuctionCodeCell} from "./NftAuction.source"; 6 | 7 | export class NftAuctionLocal { 8 | protected constructor( 9 | public readonly contract: SmartContract, 10 | public readonly address: Address 11 | ) { 12 | 13 | } 14 | 15 | async getSaleData() { 16 | let res = await this.contract.invokeGetMethod('get_sale_data', []) 17 | 18 | if (res.exit_code !== 0) { 19 | throw new Error(`Unable to invoke get_sale_data on auction contract`) 20 | } 21 | 22 | let [ 23 | saleType, 24 | end, 25 | endTimestamp, 26 | marketplaceAddressSlice, 27 | nftAddressSlice, 28 | nftOwnerAddressSlice, 29 | lastBidAmount, 30 | lastBidAddressSlice, 31 | minStep, 32 | marketplaceFeeAddressSlice, 33 | marketplaceFeeFactor, marketplaceFeeBase, 34 | royaltyAddressSlice, 35 | royaltyFactor, royaltyBase, 36 | maxBid, 37 | minBid, 38 | createdAt, 39 | lastBidAt, 40 | isCanceled, 41 | ] = res.result as [BN, BN, BN, Slice, Slice, Slice, BN, Slice, BN, Slice, BN, BN, Slice, BN, BN, BN, BN, BN, BN, BN] 42 | 43 | if (res.result.length !== 20) { 44 | throw new Error(`Unexpected length of get_sale_data expect 20 got ${res.result.length}`); 45 | } 46 | if (saleType.toNumber() !== 0x415543) { 47 | throw new Error(`Unknown sale type: ${saleType.toString()}`); 48 | } 49 | 50 | return { 51 | end: end.eqn(-1), 52 | endTimestamp: endTimestamp.toNumber(), 53 | marketplaceAddress: marketplaceAddressSlice.readAddress()!, 54 | nftAddress: nftAddressSlice.readAddress()!, 55 | nftOwnerAddress: nftOwnerAddressSlice.readAddress(), 56 | lastBidAmount, 57 | lastBidAddress: lastBidAddressSlice.readAddress(), 58 | minStep, 59 | marketplaceFeeAddress: marketplaceFeeAddressSlice.readAddress()!, 60 | marketplaceFeeFactor, marketplaceFeeBase, 61 | royaltyAddress: royaltyAddressSlice.readAddress()!, 62 | royaltyFactor, royaltyBase, 63 | maxBid, 64 | minBid, 65 | createdAt: createdAt.toNumber(), 66 | lastBidAt: lastBidAt.toNumber(), 67 | isCanceled: isCanceled.eqn(-1), 68 | } 69 | } 70 | 71 | static async createFromConfig(config: NftAuctionData) { 72 | 73 | let data = buildNftAuctionDataCell(config) 74 | let contract = await SmartContract.fromCell(NftAuctionCodeCell, data) 75 | 76 | let address = contractAddress({ 77 | workchain: 0, 78 | initialData: contract.dataCell, 79 | initialCode: contract.codeCell 80 | }) 81 | 82 | contract.setC7Config({ 83 | myself: address 84 | }) 85 | 86 | return new NftAuctionLocal(contract, address) 87 | } 88 | 89 | static async create(config: { code: Cell, data: Cell, address: Address }) { 90 | let contract = await SmartContract.fromCell(config.code, config.data) 91 | contract.setC7Config({ 92 | myself: config.address 93 | }) 94 | return new NftAuctionLocal(contract, config.address) 95 | } 96 | 97 | static async createFromContract(contract: SmartContract, address: Address) { 98 | contract.setC7Config({ 99 | myself: address 100 | }) 101 | return new NftAuctionLocal(contract, address) 102 | } 103 | } -------------------------------------------------------------------------------- /packages/contracts/nft-auction/isNftAuctionContract.ts: -------------------------------------------------------------------------------- 1 | import {NftAuctionCodeCell} from "./NftAuction.source"; 2 | import {Address, Cell} from "ton"; 3 | import {SmartContract} from "ton-contract-executor"; 4 | 5 | const NftAuctionCodeHash = NftAuctionCodeCell.hash(); 6 | 7 | export async function isNftAuctionContract(address: Address, codeCell: Cell, dataCell: Cell) { 8 | return NftAuctionCodeHash.equals(codeCell.hash()); 9 | } 10 | 11 | export async function isNftAuctionContractModern(contract: SmartContract) { 12 | return NftAuctionCodeHash.equals(contract.codeCell.hash()); 13 | } 14 | -------------------------------------------------------------------------------- /packages/contracts/nft-auction/test-tools.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell, CellMessage, CommonMessageInfo, fromNano, InternalMessage} from "ton"; 2 | import {OutAction} from "ton-contract-executor"; 3 | import BN from "bn.js"; 4 | import {NftAuctionLocal} from "./NftAuctionLocal"; 5 | 6 | declare global { 7 | namespace jest { 8 | interface Matchers { 9 | toHasMessage(expected: { 10 | to:Address, 11 | mode?:number, 12 | check?: (message:Cell) => boolean, 13 | value?: BN|BN[], 14 | }): R 15 | toOkExecuted(resultCode?:number): R 16 | } 17 | } 18 | } 19 | 20 | function isMatchValue(value:BN, check:BN|BN[]) { 21 | if (Array.isArray(check)) { 22 | return value.gte(check[0]) && value.lte(check[1]); 23 | } 24 | return value.eq(check); 25 | } 26 | 27 | function printCheckValue(check:BN|BN[]) { 28 | if (Array.isArray(check)) { 29 | return `[${fromNano(check[0])},${fromNano(check[1])}]` 30 | } 31 | return fromNano(check); 32 | } 33 | 34 | expect.extend({ 35 | toOkExecuted(received:{exit_code:number,logs:string}, params?:number) { 36 | if (received.exit_code === (params||0)) { 37 | if (!received.logs) { 38 | return { 39 | pass:true, 40 | message: () => 'ok executed', 41 | } 42 | } else { 43 | return { 44 | pass: false, 45 | message: () => `Execute failed, exit_code ${received.exit_code}, logs ${received.logs}` 46 | } 47 | } 48 | } else { 49 | return { 50 | pass: false, 51 | message: () => `Execute failed, exit_code ${received.exit_code}, expected ${params||0}` 52 | } 53 | } 54 | 55 | }, 56 | toHasMessage(received:OutAction[], params:{ 57 | to:Address, 58 | mode?:number, 59 | check?: (message:Cell) => boolean, 60 | value?: BN|BN[] 61 | }) { 62 | let log:string[] = [] 63 | for(const action of received) { 64 | const msg = transformMessage(action); 65 | if (msg && msg.to?.equals(params.to)) { 66 | if (!params.mode || params.mode === msg.mode) { 67 | if (!params.check || params.check(msg.body)) { 68 | if (!params.value || isMatchValue(msg.value, params.value)) { 69 | return { 70 | pass: true, 71 | message: () => `has message to:${params.to.toFriendly()}` 72 | } 73 | } 74 | } 75 | } 76 | } 77 | let opCode: number|string|undefined = msg?.body.beginParse().readUint(32).toNumber(); 78 | if (opCode === 1607220500) { 79 | opCode = 'transfer' 80 | } 81 | log.push(`[${action.type}] to:${msg?.to?.toFriendly()} amount:${fromNano(msg?.value||new BN(0))} mode:${msg?.mode}${opCode ? ` op=${opCode}` : ''}`) 82 | } 83 | return { 84 | pass: false, 85 | message: () => `No messages:\n\tlist:\n\t\t${log.join("\n\t\t")}${received.length === 0 ? 'action list empty': ''}\n\tsearch\n\t\tto:${params.to.toFriendly()} amount:${params.value ? printCheckValue(params.value) : 'any'} mode:${params.mode}${!!params.check ? ` check(${params.check.name})` : ''}` 86 | } 87 | } 88 | }); 89 | 90 | 91 | export function transformMessage(action: OutAction) { 92 | if (action.type === 'send_msg') { 93 | return { 94 | to: action.message.info.dest, 95 | value: action.message.info.type === 'internal' ? action.message.info.value.coins : new BN(0), 96 | body: action.message.body, 97 | mode: action.mode, 98 | } 99 | } 100 | return null; 101 | } 102 | 103 | /** 104 | * @deprecated use expect.toHasMessage() 105 | * @param actions 106 | * @param params 107 | */ 108 | export function hasMessage(actions: OutAction[], params:{ 109 | to:Address, 110 | mode?:number, 111 | check?: (message:Cell) => boolean 112 | }): boolean { 113 | for(const action of actions) { 114 | const msg = transformMessage(action); 115 | if (msg && msg.to?.equals(params.to)) { 116 | if (!params.mode || params.mode === msg.mode) { 117 | if (!params.check || params.check(msg.body)) { 118 | return true; 119 | } 120 | } 121 | } 122 | } 123 | return false; 124 | } 125 | 126 | export function isTransferPayload(cell:Cell, to: Address, forwardAmountLimit?: BN) { 127 | const slice = cell.beginParse(); 128 | const op = slice.readUint(32); 129 | slice.readUint(64); // query_id 130 | const newOwner = slice.readAddress(); 131 | slice.readAddress(); // response address 132 | slice.readUint(1); // custom payload = 0 133 | const forward = slice.readCoins(); // forward amount 134 | if (( 135 | op.eq(new BN(0x5fcc3d14)) 136 | && newOwner?.equals(to) 137 | && (!forwardAmountLimit || forward.gt(forwardAmountLimit)) )) { 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | export async function makeBid(auc: NftAuctionLocal, buyerAddress: Address, amount: BN) { 144 | return auc.contract.sendInternalMessage(new InternalMessage({ 145 | to: auc.address, 146 | from: buyerAddress, 147 | value: amount, 148 | bounce: true, 149 | body: new CommonMessageInfo({ 150 | body: new CellMessage(new Cell()) 151 | }) 152 | })); 153 | } -------------------------------------------------------------------------------- /packages/contracts/nft-collection/NftCollection.source.ts: -------------------------------------------------------------------------------- 1 | import {Cell} from "ton"; 2 | import {combineFunc} from "../../utils/combineFunc"; 3 | 4 | export const NftCollectionSource = combineFunc(__dirname, [ 5 | '../sources/stdlib.fc', 6 | '../sources/op-codes.fc', 7 | '../sources/params.fc', 8 | '../sources/nft-collection-editable.fc', 9 | ]) 10 | 11 | export const NftCollectionCodeBoc = 'te6cckECFAEAAh8AART/APSkE/S88sgLAQIBYgkCAgEgBAMAJbyC32omh9IGmf6mpqGC3oahgsQCASAIBQIBIAcGAC209H2omh9IGmf6mpqGAovgngCOAD4AsAAvtdr9qJofSBpn+pqahg2IOhph+mH/SAYQAEO4tdMe1E0PpA0z/U1NQwECRfBNDUMdQw0HHIywcBzxbMyYAgLNDwoCASAMCwA9Ra8ARwIfAFd4AYyMsFWM8WUAT6AhPLaxLMzMlx+wCAIBIA4NABs+QB0yMsCEsoHy//J0IAAtAHIyz/4KM8WyXAgyMsBE/QA9ADLAMmAE59EGOASK3wAOhpgYC42Eit8H0gGADpj+mf9qJofSBpn+pqahhBCDSenKgpQF1HFBuvgoDoQQhUZYBWuEAIZGWCqALnixJ9AQpltQnlj+WfgOeLZMAgfYBwGyi544L5cMiS4ADxgRLgAXGBEuAB8YEYGYHgAkExIREAA8jhXU1DAQNEEwyFAFzxYTyz/MzMzJ7VTgXwSED/LwACwyNAH6QDBBRMhQBc8WE8s/zMzMye1UAKY1cAPUMI43gED0lm+lII4pBqQggQD6vpPywY/egQGTIaBTJbvy9AL6ANQwIlRLMPAGI7qTAqQC3gSSbCHis+YwMlBEQxPIUAXPFhPLP8zMzMntVABgNQLTP1MTu/LhklMTugH6ANQwKBA0WfAGjhIBpENDyFAFzxYTyz/MzMzJ7VSSXwXiN0CayQ==' 12 | 13 | export const NftCollectionCodeCell = Cell.fromBoc(Buffer.from(NftCollectionCodeBoc, 'base64'))[0] -------------------------------------------------------------------------------- /packages/contracts/nft-collection/isNftCollectionContract.spec.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell, TonClient} from "ton"; 2 | import {isNftCollectionContract} from "./isNftCollectionContract"; 3 | import {SmartContract} from "ton-contract-executor"; 4 | 5 | describe('collection detector', () => { 6 | it('should detect nft collection', async () => { 7 | let client = new TonClient({ 8 | endpoint: 'https://toncenter.com/api/v2/jsonRPC', 9 | }) 10 | 11 | let address = Address.parse('EQC5RIVNDIX2pw-LHugckLEv82s9SpT7f-n-PnrQaCxcDQM6') 12 | let res = await client.getContractState(address) 13 | 14 | let code = Cell.fromBoc(res.code!)[0] 15 | let data = Cell.fromBoc(res.data!)[0] 16 | 17 | let isCollection = await isNftCollectionContract(await SmartContract.fromCell(code, data)) 18 | 19 | expect(isCollection).toBe(true) 20 | }) 21 | }) -------------------------------------------------------------------------------- /packages/contracts/nft-collection/isNftCollectionContract.ts: -------------------------------------------------------------------------------- 1 | import {Cell, Slice} from "ton"; 2 | import {NftCollectionCodeCell} from "./NftCollection.source"; 3 | import {SmartContract} from "ton-contract-executor"; 4 | import BN from "bn.js"; 5 | 6 | const NftCollectionCodeCellHash = NftCollectionCodeCell.hash() 7 | 8 | export async function isNftCollectionContract(contract: SmartContract) { 9 | let codeHash = contract.codeCell.hash() 10 | 11 | // Most common case for standard contracts 12 | if (NftCollectionCodeCellHash.equals(codeHash)) { 13 | return true 14 | } 15 | 16 | try { 17 | // (int, cell, slice) get_collection_data() 18 | let res = await contract.invokeGetMethod('get_collection_data', []) 19 | 20 | if (res.exit_code !== 0 || res.type !== 'success') { 21 | return false 22 | } 23 | 24 | if (res.result.length !== 3) { 25 | return false 26 | } 27 | 28 | let [index, content, owner] = res.result 29 | 30 | if (!(index instanceof BN)) { 31 | return false 32 | } 33 | 34 | if (!(content instanceof Cell)) { 35 | return false 36 | } 37 | 38 | if (!(owner instanceof Slice)) { 39 | return false 40 | } 41 | 42 | return true 43 | } catch (e) { 44 | return false 45 | } 46 | } -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v2/NftFixpriceSaleV2.data.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell, contractAddress, StateInit} from "ton"; 2 | import BN from "bn.js"; 3 | import {NftFixPriceSaleV2CodeCell} from "./NftFixpriceSaleV2.source"; 4 | 5 | export type NftFixPriceSaleV2Data = { 6 | isComplete: boolean 7 | createdAt: number 8 | marketplaceAddress: Address 9 | nftAddress: Address 10 | nftOwnerAddress: Address|null 11 | fullPrice: BN 12 | marketplaceFeeAddress: Address 13 | marketplaceFee: BN 14 | royaltyAddress: Address 15 | royaltyAmount: BN 16 | } 17 | 18 | export function buildNftFixPriceSaleV2DataCell(data: NftFixPriceSaleV2Data) { 19 | 20 | let feesCell = new Cell() 21 | 22 | feesCell.bits.writeAddress(data.marketplaceFeeAddress) 23 | feesCell.bits.writeCoins(data.marketplaceFee) 24 | feesCell.bits.writeAddress(data.royaltyAddress) 25 | feesCell.bits.writeCoins(data.royaltyAmount) 26 | 27 | let dataCell = new Cell() 28 | 29 | dataCell.bits.writeUint(data.isComplete ? 1 : 0, 1) 30 | dataCell.bits.writeUint(data.createdAt, 32) 31 | dataCell.bits.writeAddress(data.marketplaceAddress) 32 | dataCell.bits.writeAddress(data.nftAddress) 33 | dataCell.bits.writeAddress(data.nftOwnerAddress) 34 | dataCell.bits.writeCoins(data.fullPrice) 35 | dataCell.refs.push(feesCell) 36 | 37 | return dataCell 38 | } 39 | 40 | export function buildNftFixPriceSaleV2StateInit(data: Omit) { 41 | let dataCell = buildNftFixPriceSaleV2DataCell({ 42 | ...data, 43 | // Nft owner address would be set by NFT itself by ownership_assigned callback 44 | nftOwnerAddress: null, 45 | isComplete: false, 46 | }) 47 | 48 | let stateInit = new StateInit({ 49 | code: NftFixPriceSaleV2CodeCell, 50 | data: dataCell 51 | }) 52 | let address = contractAddress({workchain: 0, initialCode: NftFixPriceSaleV2CodeCell, initialData: dataCell}) 53 | 54 | return { 55 | address, 56 | stateInit 57 | } 58 | } 59 | 60 | export const OperationCodes = { 61 | AcceptCoins: 1, 62 | Buy: 2, 63 | CancelSale: 3, 64 | } 65 | 66 | export const Queries = { 67 | cancelSale: (params: { queryId?: number }) => { 68 | let msgBody = new Cell() 69 | msgBody.bits.writeUint(OperationCodes.CancelSale, 32) 70 | msgBody.bits.writeUint(params.queryId ?? 0, 64) 71 | return msgBody 72 | } 73 | } -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v2/NftFixpriceSaleV2.source.ts: -------------------------------------------------------------------------------- 1 | import {Cell} from "ton"; 2 | import {combineFunc} from "../../utils/combineFunc"; 3 | 4 | export const NftFixPriceSaleSourceV2 = combineFunc(__dirname, [ 5 | '../sources/stdlib.fc', 6 | '../sources/op-codes.fc', 7 | '../sources/nft-fixprice-sale-v2.fc', 8 | ]) 9 | 10 | export const NftFixPriceSaleV2CodeBoc = 'te6cckECDAEAAikAART/APSkE/S88sgLAQIBIAMCAATyMAIBSAUEAFGgOFnaiaGmAaY/9IH0gfSB9AGoYaH0gfQB9IH0AGEEIIySsKAVgAKrAQICzQgGAfdmCEDuaygBSYKBSML7y4cIk0PpA+gD6QPoAMFOSoSGhUIehFqBSkHCAEMjLBVADzxYB+gLLaslx+wAlwgAl10nCArCOF1BFcIAQyMsFUAPPFgH6AstqyXH7ABAjkjQ04lpwgBDIywVQA88WAfoCy2rJcfsAcCCCEF/MPRSBwCCIYAYyMsFKs8WIfoCy2rLHxPLPyPPFlADzxbKACH6AsoAyYMG+wBxVVAGyMsAFcsfUAPPFgHPFgHPFgH6AszJ7VQC99AOhpgYC42EkvgnB9IBh2omhpgGmP/SB9IH0gfQBqGBNgAPloyhFrpOEBWccgGRwcKaDjgskvhHAoomOC+XD6AmmPwQgCicbIiV15cPrpn5j9IBggKwNkZYAK5Y+oAeeLAOeLAOeLAP0BZmT2qnAbE+OAcYED6Y/pn5gQwLCQFKwAGSXwvgIcACnzEQSRA4R2AQJRAkECPwBeA6wAPjAl8JhA/y8AoAyoIQO5rKABi+8uHJU0bHBVFSxwUVsfLhynAgghBfzD0UIYAQyMsFKM8WIfoCy2rLHxnLPyfPFifPFhjKACf6AhfKAMmAQPsAcQZQREUVBsjLABXLH1ADzxYBzxYBzxYB+gLMye1UABY3EDhHZRRDMHDwBTThaBI=' 11 | 12 | export const NftFixPriceSaleV2CodeCell = Cell.fromBoc(Buffer.from(NftFixPriceSaleV2CodeBoc, 'base64'))[0] -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v2/NftFixpriceSaleV2Local.ts: -------------------------------------------------------------------------------- 1 | import {SmartContract} from "ton-contract-executor"; 2 | import {Address, Cell, contractAddress, Slice} from "ton"; 3 | import BN from "bn.js"; 4 | import {buildNftFixPriceSaleV2DataCell, NftFixPriceSaleV2Data, Queries} from "./NftFixpriceSaleV2.data"; 5 | import {NftFixPriceSaleSourceV2} from "./NftFixpriceSaleV2.source"; 6 | import {compileFunc} from "../../utils/compileFunc"; 7 | 8 | export class NftFixpriceSaleV2Local { 9 | private constructor( 10 | public readonly contract: SmartContract, 11 | public readonly address: Address 12 | ) { 13 | 14 | } 15 | 16 | static queries = Queries 17 | 18 | async getSaleData() { 19 | let res = await this.contract.invokeGetMethod('get_sale_data', []) 20 | 21 | if (res.exit_code !== 0) { 22 | throw new Error(`Unable to invoke get_sale_data on sale contract`) 23 | } 24 | 25 | let [ 26 | saleType, 27 | isComplete, 28 | createdAt, 29 | marketplaceAddressSlice, 30 | nftAddressSlice, 31 | nftOwnerAddressSlice, 32 | fullPrice, 33 | marketplaceFeeAddressSlice, 34 | marketplaceFee, 35 | royaltyAddressSlice, 36 | royaltyAmount, 37 | ] = res.result as [BN, BN, BN, Slice, Slice, Slice, BN, Slice, BN, Slice, BN] 38 | 39 | if (saleType.toNumber() !== 0x46495850) { 40 | throw new Error(`Unknown sale type: ${saleType.toString()}`) 41 | } 42 | 43 | return { 44 | isComplete: isComplete.eqn(-1), 45 | createdAt: createdAt.toNumber(), 46 | marketplaceAddress: marketplaceAddressSlice.readAddress()!, 47 | nftAddress: nftAddressSlice.readAddress()!, 48 | nftOwnerAddress: nftOwnerAddressSlice.readAddress(), 49 | fullPrice, 50 | marketplaceFeeAddress: marketplaceFeeAddressSlice.readAddress()!, 51 | marketplaceFee, 52 | royaltyAddress: royaltyAddressSlice.readAddress()!, 53 | royaltyAmount, 54 | } 55 | } 56 | 57 | static async createFromConfig(config: NftFixPriceSaleV2Data) { 58 | let code = await compileFunc(NftFixPriceSaleSourceV2) 59 | 60 | let data = buildNftFixPriceSaleV2DataCell(config) 61 | let contract = await SmartContract.fromCell(code.cell, data) 62 | 63 | let address = contractAddress({ 64 | workchain: 0, 65 | initialData: contract.dataCell, 66 | initialCode: contract.codeCell 67 | }) 68 | 69 | contract.setC7Config({ 70 | myself: address 71 | }) 72 | 73 | return new NftFixpriceSaleV2Local(contract, address) 74 | } 75 | 76 | static async create(config: { code: Cell, data: Cell, address: Address }) { 77 | let contract = await SmartContract.fromCell(config.code, config.data) 78 | contract.setC7Config({ 79 | myself: config.address 80 | }) 81 | return new NftFixpriceSaleV2Local(contract, config.address) 82 | } 83 | 84 | static async createFromContract(contract: SmartContract, address: Address) { 85 | contract.setC7Config({ 86 | myself: address 87 | }) 88 | return new NftFixpriceSaleV2Local(contract, address) 89 | } 90 | } -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v2/isNftFixPriceSaleV2Contract.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell} from "ton"; 2 | import {NftFixPriceSaleV2CodeCell} from "./NftFixpriceSaleV2.source"; 3 | import {SmartContract} from "ton-contract-executor"; 4 | 5 | const NftFixPriceSaleV2CodeCellHash = NftFixPriceSaleV2CodeCell.hash() 6 | 7 | export async function isNftFixPriceSaleV2Contract(address: Address, codeCell: Cell, dataCell: Cell) { 8 | if (NftFixPriceSaleV2CodeCellHash.equals(codeCell.hash())) { 9 | return true 10 | } 11 | } 12 | 13 | export async function isNftFixPriceSaleV2ContractModern(contract: SmartContract) { 14 | if (NftFixPriceSaleV2CodeCellHash.equals(contract.codeCell.hash())) { 15 | return true 16 | } 17 | } -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v3/NftFixpriceSaleV3.data.ts: -------------------------------------------------------------------------------- 1 | import { Address, Builder, Cell, contractAddress, StateInit } from 'ton' 2 | import BN from 'bn.js' 3 | import { NftFixPriceSaleV3CodeCell } from './NftFixpriceSaleV3.source' 4 | 5 | export type NftFixPriceSaleV3Data = { 6 | isComplete: boolean 7 | createdAt: number 8 | marketplaceAddress: Address 9 | nftAddress: Address 10 | nftOwnerAddress: Address | null 11 | fullPrice: BN 12 | marketplaceFeeAddress: Address 13 | marketplaceFee: BN 14 | royaltyAddress: Address 15 | royaltyAmount: BN 16 | canDeployByExternal?: boolean 17 | } 18 | 19 | export function buildNftFixPriceSaleV3DataCell(data: NftFixPriceSaleV3Data) { 20 | const feesCell = new Cell() 21 | 22 | feesCell.bits.writeAddress(data.marketplaceFeeAddress) 23 | feesCell.bits.writeCoins(data.marketplaceFee) 24 | feesCell.bits.writeAddress(data.royaltyAddress) 25 | feesCell.bits.writeCoins(data.royaltyAmount) 26 | 27 | const dataCell = new Cell() 28 | 29 | dataCell.bits.writeUint(data.isComplete ? 1 : 0, 1) 30 | dataCell.bits.writeUint(data.createdAt, 32) 31 | dataCell.bits.writeAddress(data.marketplaceAddress) 32 | dataCell.bits.writeAddress(data.nftAddress) 33 | dataCell.bits.writeAddress(data.nftOwnerAddress) 34 | dataCell.bits.writeCoins(data.fullPrice) 35 | dataCell.refs.push(feesCell) 36 | dataCell.bits.writeUint(data.canDeployByExternal ? 1 : 0, 1) // can_deploy_by_external 37 | 38 | return dataCell 39 | } 40 | 41 | export function buildNftFixPriceSaleV3StateInit( 42 | data: Omit 43 | ) { 44 | const dataCell = buildNftFixPriceSaleV3DataCell({ 45 | ...data, 46 | // Nft owner address would be set by NFT itself by ownership_assigned callback 47 | nftOwnerAddress: null, 48 | isComplete: false, 49 | }) 50 | 51 | const stateInit = new StateInit({ 52 | code: NftFixPriceSaleV3CodeCell, 53 | data: dataCell, 54 | }) 55 | const address = contractAddress({ 56 | workchain: 0, 57 | initialCode: NftFixPriceSaleV3CodeCell, 58 | initialData: dataCell, 59 | }) 60 | 61 | return { 62 | address, 63 | stateInit, 64 | } 65 | } 66 | 67 | export const OperationCodes = { 68 | AcceptCoins: 1, 69 | Buy: 2, 70 | CancelSale: 3, 71 | ChangePrice: 0x6c6c2080, 72 | } 73 | 74 | export const Queries = { 75 | cancelSale: (params: { queryId?: number }) => { 76 | const msgBody = new Cell() 77 | msgBody.bits.writeUint(OperationCodes.CancelSale, 32) 78 | msgBody.bits.writeUint(params.queryId ?? 0, 64) 79 | return msgBody 80 | }, 81 | deployMsg: (params: { queryId?: number }) => { 82 | const msgBody = new Cell() 83 | msgBody.bits.writeUint(OperationCodes.AcceptCoins, 32) 84 | msgBody.bits.writeUint(params.queryId ?? 0, 64) 85 | return msgBody 86 | }, 87 | changePrice: (params: { price: BN, marketplaceFee: BN, royaltyAmount: BN }) => { 88 | const msgBody = new Cell() 89 | msgBody.bits.writeUint(OperationCodes.ChangePrice, 32) 90 | msgBody.bits.writeUint(0, 64) // query id 91 | 92 | msgBody.bits.writeCoins(params.price) 93 | msgBody.bits.writeCoins(params.marketplaceFee) 94 | msgBody.bits.writeCoins(params.royaltyAmount) 95 | 96 | return msgBody 97 | }, 98 | buyMessage(param: { queryId: bigint }) { 99 | return new Builder().storeUint(OperationCodes.Buy, 32) 100 | .storeUint(new BN(param.queryId.toString()), 64) 101 | .endCell() 102 | }, 103 | } 104 | 105 | export type NftFixPriceSaleV3R3Data = { 106 | isComplete: boolean 107 | createdAt: number 108 | marketplaceAddress: Address 109 | nftAddress: Address 110 | nftOwnerAddress: Address | null 111 | fullPrice: BN 112 | marketplaceFeeAddress: Address 113 | marketplaceFee: BN 114 | royaltyAddress: Address 115 | royaltyAmount: BN 116 | soldAt: number 117 | queryId: BN 118 | } 119 | 120 | export function buildNftFixPriceSaleV3R3DataCell(data: NftFixPriceSaleV3Data | NftFixPriceSaleV3R3Data) { 121 | const feesCell = new Cell() 122 | 123 | feesCell.bits.writeAddress(data.marketplaceFeeAddress) 124 | feesCell.bits.writeCoins(data.marketplaceFee) 125 | feesCell.bits.writeAddress(data.royaltyAddress) 126 | feesCell.bits.writeCoins(data.royaltyAmount) 127 | 128 | const dataCell = new Cell() 129 | 130 | dataCell.bits.writeUint(data.isComplete ? 1 : 0, 1) 131 | dataCell.bits.writeUint(data.createdAt, 32) 132 | dataCell.bits.writeAddress(data.marketplaceAddress) 133 | dataCell.bits.writeAddress(data.nftAddress) 134 | dataCell.bits.writeAddress(data.nftOwnerAddress) 135 | dataCell.bits.writeCoins(data.fullPrice) 136 | dataCell.refs.push(feesCell) 137 | dataCell.bits.writeUint('soldAt' in data ? data.soldAt : 0, 32) 138 | dataCell.bits.writeUint('queryId' in data ? data.queryId : 0, 64) 139 | 140 | return dataCell 141 | } 142 | -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v3/NftFixpriceSaleV3.source.ts: -------------------------------------------------------------------------------- 1 | import { Cell } from 'ton' 2 | import { combineFunc } from "../../utils/combineFunc"; 3 | 4 | export const NftFixPriceSaleSourceV3 = () => { 5 | return combineFunc(__dirname, [ 6 | '../sources/stdlib.fc', 7 | '../sources/op-codes.fc', 8 | '../sources/nft-fixprice-sale-v3.fc', 9 | ]) 10 | } 11 | 12 | export const NftFixPriceSaleSourceV3R2 = () => { 13 | return combineFunc(__dirname, [ 14 | '../sources/stdlib.fc', 15 | '../sources/op-codes.fc', 16 | '../sources/nft-fixprice-sale-v3r2.fc', 17 | ]) 18 | } 19 | 20 | const NftFixPriceSaleV3CodeBoc = 21 | 'te6cckECDAEAAqAAART/APSkE/S88sgLAQIBIAMCAH7yMO1E0NMA0x/6QPpA+kD6ANTTADDAAY4d+ABwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VTgXweCAP/+8vACAUgFBABXoDhZ2omhpgGmP/SB9IH0gfQBqaYAYGGh9IH0AfSB9ABhBCCMkrCgFYACqwECAs0IBgH3ZghA7msoAUmCgUjC+8uHCJND6QPoA+kD6ADBTkqEhoVCHoRagUpBwgBDIywVQA88WAfoCy2rJcfsAJcIAJddJwgKwjhdQRXCAEMjLBVADzxYB+gLLaslx+wAQI5I0NOJacIAQyMsFUAPPFgH6AstqyXH7AHAgghBfzD0UgcAlsjLHxPLPyPPFlADzxbKAIIJycOA+gLKAMlxgBjIywUmzxZw+gLLaszJgwb7AHFVUHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVAP10A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppgBgYOCmE44BgAEwthGmP6Z+lVW8Q4AHxgRDAgRXdFOAA2CnT44LYTwhWL4ZqGGhpg+oYAP2AcBRgAPloyhJrpOEBWfGBHByUYABOGxuIHCOyiiGYOHgC8BRgAMCwoJAC6SXwvgCMACmFVEECQQI/AF4F8KhA/y8ACAMDM5OVNSxwWSXwngUVHHBfLh9IIQBRONkRW68uH1BPpAMEBmBXAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVADYMTc4OYIQO5rKABi+8uHJU0bHBVFSxwUVsfLhynAgghBfzD0UIYAQyMsFKM8WIfoCy2rLHxXLPyfPFifPFhTKACP6AhPKAMmAQPsAcVBmRRUEcAfIywAWyx9QBM8WWM8WAc8WAfoCzMsAye1UM/Vflw==' 22 | 23 | export const NftFixPriceSaleV3CodeCell = Cell.fromBoc(Buffer.from(NftFixPriceSaleV3CodeBoc, 'base64'))[0] 24 | 25 | export const NftFixPriceSaleV3R2CodeBoc = 'te6cckECCwEAArkAART/APSkE/S88sgLAQIBIAMCAH7yMO1E0NMA0x/6QPpA+kD6ANTTADDAAY4d+ABwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VTgXweCAP/+8vACAUgFBABXoDhZ2omhpgGmP/SB9IH0gfQBqaYAYGGh9IH0AfSB9ABhBCCMkrCgFYACqwECAs0IBgH3ZghA7msoAUmCgUjC+8uHCJND6QPoA+kD6ADBTkqEhoVCHoRagUpBwgBDIywVQA88WAfoCy2rJcfsAJcIAJddJwgKwjhdQRXCAEMjLBVADzxYB+gLLaslx+wAQI5I0NOJacIAQyMsFUAPPFgH6AstqyXH7AHAgghBfzD0UgcAlsjLHxPLPyPPFlADzxbKAIIJycOA+gLKAMlxgBjIywUmzxZw+gLLaszJgwb7AHFVUHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVAH30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppgBgYOCmE44BgAEqYhOmPhW8Q4YBKGATpn8cIxbMbC3MbK2QV44LJOZlvKAVxFWAAyS+G8BJrpOEBFcCBFd0VYACRWdjYKdxjgthOjq+G6hhoaYPqGAD9gHAU4ADAkB6PLRlLOOQjEzOTlTUscFkl8J4FFRxwXy4fSCEAUTjZEWuvLh9QP6QDBGUBA0WXAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVOAwNyjAA+MCKMAAnDY3EDhHZRRDMHDwBeAIwAKYVUQQJBAj8AXgXwqED/LwCgDUODmCEDuaygAYvvLhyVNGxwVRUscFFbHy4cpwIIIQX8w9FCGAEMjLBSjPFiH6Astqyx8Vyz8nzxYnzxYUygAj+gITygDJgwb7AHFQZkUVBHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVOBqUYM=' 26 | 27 | export const NftFixPriceSaleV3R2CodeCell = Cell.fromBoc(Buffer.from(NftFixPriceSaleV3R2CodeBoc, 'base64'))[0] 28 | 29 | export const NftFixPriceSaleV3R3CodeBoc = 'te6ccgECDwEAA5MAART/APSkE/S88sgLAQIBYgIDAgLNBAUCASANDgL30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppj+mfmBg4KYVjgGAASpiFaY+F7xDhgEoYBWmfxwjFsxsLcxsrZBZjgsk5mW8oBfEV4ADJL4dwEuuk4QEWQIEV3RXgAJFZ2Ngp5OOC2HGBFWAA+WjKFkEINjYQQF1AYHAdFmCEAX14QBSYKBSML7y4cIk0PpA+gD6QPoAMFOSoSGhUIehFqBSkCH6RFtwgBDIywVQA88WAfoCy2rJcfsAJcIAJddJwgKwjhtQRSH6RFtwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWoMAGQwMWyy1DDQ0wchgCCw8tGVIsMAjhSBAlj4I1NBobwE+CMCoLkTsPLRlpEy4gHUMAH7AATwU8fHBbCOXRNfAzI3Nzc3BPoA+gD6ADBTIaEhocEB8tGYBdD6QPoA+kD6ADAwyDICzxZY+gIBzxZQBPoCyXAgEEgQNxBFEDQIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVOCz4wIwMTcowAPjAijAAOMCCMACCAkKCwCGNTs7U3THBZJfC+BRc8cF8uH0ghAFE42RGLry4fX6QDAQSBA3VTIIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVADiODmCEAX14QAYvvLhyVNGxwVRUscFFbHy4cpwIIIQX8w9FCGAEMjLBSjPFiH6Astqyx8Vyz8nzxYnzxYUygAj+gITygDJgwb7AHFwVBcAXjMQNBAjCMjLABfLH1AFzxZQA88WAc8WAfoCzMsfyz/J7VQAGDY3EDhHZRRDMHDwBQAgmFVEECQQI/AF4F8KhA/y8ADsIfpEW3CAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FMjLH1Iwyz8kzxZQBM8WE8oAggnJw4D6AhLKAMlxgBjIywUnzxZw+gLLaswl+kRbyYMG+wBxVWD4IwEIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVACHvOFnaiaGmAaY/9IH0gfSB9AGppj+mfmC3ofSB9AH0gfQAYKaFQkNDggPlozJP9Ii2TfSItkf0iLcEIIySsKAVgAKrAQAgb7l72omhpgGmP/SB9IH0gfQBqaY/pn5gBaH0gfQB9IH0AGCmxUJDQ4ID5aM0U/SItlH0iLZH9Ii2F4ACFiBqqiU' // func:0.4.4 src:op-codes.fc, imports/stdlib.fc, nft-fixprice-sale-v3r3.fc 30 | export const NftFixPriceSaleV3R3CodeCell = Cell.fromBoc(Buffer.from(NftFixPriceSaleV3R3CodeBoc, 'base64'))[0] 31 | -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v3/NftFixpriceSaleV3Local.ts: -------------------------------------------------------------------------------- 1 | import { SmartContract } from 'ton-contract-executor' 2 | import { Address, Cell, contractAddress, Slice } from 'ton' 3 | import BN from 'bn.js' 4 | import { buildNftFixPriceSaleV3DataCell, NftFixPriceSaleV3Data, Queries } from './NftFixpriceSaleV3.data' 5 | import { NftFixPriceSaleSourceV3 } from './NftFixpriceSaleV3.source' 6 | import { compileFunc } from "../../utils/compileFunc"; 7 | 8 | export class NftFixpriceSaleV3Local { 9 | private constructor(public readonly contract: SmartContract, public readonly address: Address) {} 10 | 11 | static queries = Queries 12 | 13 | async getSaleData() { 14 | const res = await this.contract.invokeGetMethod('get_sale_data', []) 15 | 16 | if (res.exit_code !== 0) { 17 | throw new Error('Unable to invoke get_sale_data on sale contract') 18 | } 19 | 20 | const [ 21 | saleType, 22 | isComplete, 23 | createdAt, 24 | marketplaceAddressSlice, 25 | nftAddressSlice, 26 | nftOwnerAddressSlice, 27 | fullPrice, 28 | marketplaceFeeAddressSlice, 29 | marketplaceFee, 30 | royaltyAddressSlice, 31 | royaltyAmount, 32 | ] = res.result as [BN, BN, BN, Slice, Slice, Slice, BN, Slice, BN, Slice, BN] 33 | 34 | if (saleType.toNumber() !== 0x46495850) { 35 | throw new Error(`Unknown sale type: ${saleType.toString()}`) 36 | } 37 | 38 | return { 39 | isComplete: isComplete.eqn(-1), 40 | createdAt: createdAt.toNumber(), 41 | marketplaceAddress: marketplaceAddressSlice.readAddress()!, 42 | nftAddress: nftAddressSlice.readAddress()!, 43 | nftOwnerAddress: nftOwnerAddressSlice.readAddress(), 44 | fullPrice, 45 | marketplaceFeeAddress: marketplaceFeeAddressSlice.readAddress()!, 46 | marketplaceFee, 47 | royaltyAddress: royaltyAddressSlice.readAddress()!, 48 | royaltyAmount, 49 | } 50 | } 51 | 52 | static async createFromConfig(config: NftFixPriceSaleV3Data) { 53 | const code = await compileFunc(NftFixPriceSaleSourceV3()) 54 | 55 | const data = buildNftFixPriceSaleV3DataCell(config) 56 | const contract = await SmartContract.fromCell(code.cell, data) 57 | 58 | const address = contractAddress({ 59 | workchain: 0, 60 | initialData: contract.dataCell, 61 | initialCode: contract.codeCell, 62 | }) 63 | 64 | contract.setC7Config({ 65 | myself: address, 66 | }) 67 | 68 | return new NftFixpriceSaleV3Local(contract, address) 69 | } 70 | 71 | static async create(config: { code: Cell; data: Cell; address: Address }) { 72 | const contract = await SmartContract.fromCell(config.code, config.data) 73 | contract.setC7Config({ 74 | myself: config.address, 75 | }) 76 | return new NftFixpriceSaleV3Local(contract, config.address) 77 | } 78 | 79 | static async createFromContract(contract: SmartContract, address: Address) { 80 | contract.setC7Config({ 81 | myself: address, 82 | }) 83 | return new NftFixpriceSaleV3Local(contract, address) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v3/description-ru.md: -------------------------------------------------------------------------------- 1 | 2 | # Контракт продажи за фиксированную цену 3 | 4 | Этот контракт используется когда продавец хочет продать нфт за фиксированную цену в тонах. Продавец деплоит контракт 5 | продажи и передает нфт на этот контракт. Покупатель отправляет на контракт продажи тоны + ~0.1 для оплаты газа. 6 | Контракт передает нфт покупателю, а тоны продавцу, после этого покупка считается совершенной. Продавец может отменить продажу 7 | в любой момент до покупки. 8 | 9 | Перед покупкой необходимо убедиться что нфт достаточно 0.1 тон для смены владельца, если этого недостаточно то надо прислать 10 | больше тонов. Если нет возможности рассчитать сумму заранее, то для совершения покупки рекомендуется прислать full_price + 1 тон. 11 | 12 | Все сообщения на этот контракт должны быть отправить с bounce флагом, контракт выбрасывает ошибки. 13 | 14 | ### get_fix_price_data 15 | 1. int is_complete - -1 означает что продажа уже совершена или отменена 16 | 2. int created_at timestamp время создания контракта, используется чтобы одинаковые контракты имели разные адреса 17 | 3. slice(MsgAddress) marketplace_address адрес контракта маркетплейса, этот адрес может отменить продажу и может управлять контрактом после продажи 18 | 4. slice(MsgAddress) nft_address адрес нфт 19 | 5. slice(MsgAddress|null) nft_owner_address адрес владельца нфт, этот адрес может отменить продажу и получит тоны в случае покупки 20 | 6. int full_price цена нфт 21 | 7. slice(MsgAddress) marketplace_fee_address адрес для комиссии маркетплейса 22 | 8. int marketplace_fee размер комиссии маркетплейса 23 | 9. slice(MsgAddress) royalty_address адрес для роялти коллекции 24 | 10. int royalty_amount размер роялти коллекции 25 | 11. int sold_at timestamp дата продажи нфт если продажа состоялась 26 | 12. int query_id id запроса который привел к продаже нфт 27 | 28 | ### Рекомендуемые проверки перед использованием контракта 29 | 30 | - хеш кода контракта должен совпадать с эталонным, нельзя полагаться только на гет методы 31 | - get_fix_price_data должен вызываться нормально, без ошибок 32 | - поля nft_owner_address, nft_address, marketplace_fee_address, royalty_address должны быть заполнены и содержать валидные адреса MsgAddress workchain 0 33 | - full_price должен быть больше чем сумма royalty_amount и marketplace_fee 34 | - is_complete равен 0, если это не так, то нет смысла отправлять тоны на этот контракт 35 | - необходимо проверить что нфт, на которую ссылается контракт продажи (nft_address), действительно принадлежит ему, то-есть у нфт owner_address это адрес контракта продажи 36 | -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v3/isNftFixPriceSaleV3Contract.ts: -------------------------------------------------------------------------------- 1 | import { Address, Cell } from 'ton' 2 | import { NftFixPriceSaleV3CodeCell } from './NftFixpriceSaleV3.source' 3 | import { SmartContract } from 'ton-contract-executor' 4 | 5 | const NftFixPriceSaleV3CodeCellHash = NftFixPriceSaleV3CodeCell.hash() 6 | 7 | export async function isNftFixPriceSaleV3Contract(address: Address, codeCell: Cell, _dataCell: Cell) { 8 | if (NftFixPriceSaleV3CodeCellHash.equals(codeCell.hash())) { 9 | return true 10 | } 11 | } 12 | 13 | export async function isNftFixPriceSaleV3ContractModern(contract: SmartContract) { 14 | if (NftFixPriceSaleV3CodeCellHash.equals(contract.codeCell.hash())) { 15 | return true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale-v4/description-ru.md: -------------------------------------------------------------------------------- 1 | # Контракт продажи за фиксированную цену в тонах или жетонах 2 | 3 | Этот контракт для продажи нфт за фиксированную цену в тонах или жетонах. Продавец деплоит контракт 4 | продажи и передает нфт на этот контракт. Покупатель отправляет на контракт продажи тоны/жетоны + ~0.1/0.26 TON для оплаты газа. 5 | Контракт передает нфт покупателю, а тоны/жетоны продавцу, после этого покупка считается совершенной. 6 | Продавец может отменить продажу в любой момент до покупки. 7 | 8 | Перед покупкой необходимо убедиться что нфт достаточно 0.1 тон для смены владельца, если этого недостаточно то надо прислать 9 | больше тонов. Если нет возможности рассчитать сумму заранее, то для совершения покупки рекомендуется прислать 1 тон для оплаты газа, неиспользованные тоны вернуться отправителю. 10 | 11 | Все сообщения на этот контракт должны быть отправить с bounce флагом, контракт выбрасывает ошибки. 12 | 13 | ### get_fix_price_data_v4 14 | 1. int is_complete - -1 означает что продажа уже совершена или отменена 15 | 2. int created_at timestamp время создания контракта, используется чтобы одинаковые контракты имели разные адреса 16 | 3. slice(MsgAddress) marketplace_address адрес контракта маркетплейса, этот адрес может отменить продажу и может управлять контрактом после продажи 17 | 4. slice(MsgAddress) nft_address адрес нфт 18 | 5. slice(MsgAddress|null) nft_owner_address адрес владельца нфт, этот адрес может отменить продажу и получит тоны в случае покупки 19 | 6. int full_price цена нфт в тонах, если значение ноль то нфт нельзя купить за тоны 20 | 7. slice(MsgAddress) fee_address адрес для комиссии маркетплейса 21 | 8. int fee_percent процент комиссии маркетплейса умноженный 100000, например значение 5000 означает что комиссия маркетплейса - 5% 5000/100000=0.05 22 | 9. slice(MsgAddress) royalty_address адрес для роялти коллекции 23 | 10. int royalty_percent процент роялти коллекции 24 | 11. int sold_at timestamp дата продажи нфт если продажа состоялась 25 | 12. int sold_query_id id запроса который привел к продаже нфт 26 | 13. jetton_price_dict словарь с ценами в жетонах, ключ словаря это хеш адреса jetton wallet, значение это (Coins, MsgAddress) - MsgAddress это адрес jetton master 27 | 28 | ### Рекомендуемые проверки перед использованием контракта 29 | 30 | - хеш кода контракта должен совпадать с эталонным, нельзя полагаться только на гет методы 31 | - get_fix_price_data_v4 должен вызываться нормально, без ошибок 32 | - поля nft_owner_address, nft_address, fee_address, royalty_address должны быть заполнены и содержать валидные адреса MsgAddress workchain 0 33 | - is_complete равен 0, если это не так, то нет смысла отправлять тоны на этот контракт 34 | - необходимо проверить что нфт, на которую ссылается контракт продажи (nft_address), действительно принадлежит ему, то-есть у нфт owner_address это адрес контракта продажи 35 | - fee_percent и royalty_percent в сумме должны быть меньше 100000, что означает что сумма комиссий меньше чеи 100% 36 | -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale/NftFixPriceSale.data.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell, contractAddress, StateInit} from "ton"; 2 | import BN from "bn.js"; 3 | import {NftFixPriceSaleCodeCell} from "./NftFixPriceSale.source"; 4 | 5 | export type NftFixPriceSaleData = { 6 | marketplaceAddress: Address 7 | nftAddress: Address 8 | nftOwnerAddress: Address|null 9 | fullPrice: BN 10 | marketplaceFee: BN 11 | marketplaceFeeAddress: Address 12 | royaltyAmount: BN 13 | royaltyAddress: Address 14 | } 15 | 16 | export function buildNftFixPriceSaleDataCell(data: NftFixPriceSaleData) { 17 | 18 | let feesCell = new Cell() 19 | 20 | feesCell.bits.writeCoins(data.marketplaceFee) 21 | feesCell.bits.writeAddress(data.marketplaceFeeAddress) 22 | feesCell.bits.writeAddress(data.royaltyAddress) 23 | feesCell.bits.writeCoins(data.royaltyAmount) 24 | 25 | let dataCell = new Cell() 26 | 27 | dataCell.bits.writeAddress(data.marketplaceAddress) 28 | dataCell.bits.writeAddress(data.nftAddress) 29 | dataCell.bits.writeAddress(data.nftOwnerAddress) 30 | dataCell.bits.writeCoins(data.fullPrice) 31 | dataCell.refs.push(feesCell) 32 | 33 | return dataCell 34 | } 35 | 36 | export function buildNftFixPriceSaleStateInit(data: Omit) { 37 | let dataCell = buildNftFixPriceSaleDataCell({ 38 | ...data, 39 | // Nft owner address would be set by NFT itself by ownership_assigned callback 40 | nftOwnerAddress: null 41 | }) 42 | 43 | let stateInit = new StateInit({ 44 | code: NftFixPriceSaleCodeCell, 45 | data: dataCell 46 | }) 47 | let address = contractAddress({workchain: 0, initialCode: NftFixPriceSaleCodeCell, initialData: dataCell}) 48 | 49 | return { 50 | address, 51 | stateInit 52 | } 53 | } -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale/NftFixPriceSale.source.ts: -------------------------------------------------------------------------------- 1 | import {Cell} from "ton"; 2 | import {combineFunc} from "../../utils/combineFunc"; 3 | 4 | export const NftFixPriceSaleSource = combineFunc(__dirname, [ 5 | '../sources/stdlib.fc', 6 | '../sources/op-codes.fc', 7 | '../sources/nft-fixprice-sale.fc', 8 | ]) 9 | 10 | export const NftFixPriceSaleCodeBoc = 'te6cckECCgEAAbIAART/APSkE/S88sgLAQIBIAMCAATyMAIBSAUEADegOFnaiaH0gfSB9IH0AahhofQB9IH0gfQAYCBHAgLNCAYB99G8EIHc1lACkgUCkQX3lw4QFofQB9IH0gfQAYOEAIZGWCqATniyi6UJDQqFrQilAK/QEK5bVkuP2AOEAIZGWCrGeLKAP9AQtltWS4/YA4QAhkZYKsZ4ssfQFltWS4/YA4EEEIL+YeihDADGRlgqgC54sRfQEKZbUJ5Y+JwHAC7LPyPPFlADzxYSygAh+gLKAMmBAKD7AAH30A6GmBgLjYSS+CcH0gGHaiaH0gfSB9IH0AahgRa6ThAVnHHZkbGymQ44LJL4NwKJFjgvlw+gFpj8EIAonGyIldeXD66Z+Y/SAYIBpkKALniygB54sA54sA/QFmZPaqcBNjgEybCBsimYI4eAJwA2mP6Z+YEOAAyS+FcBDAkAtsACmjEQRxA2RUAS8ATgMjQ0NDXAA449ghA7msoAE77y4clwIIIQX8w9FCGAEMjLBVAHzxYi+gIWy2oVyx8Tyz8hzxYBzxYSygAh+gLKAMmBAKD7AOBfBIQP8vCVeDe4' 11 | 12 | export const NftFixPriceSaleCodeCell = Cell.fromBoc(Buffer.from(NftFixPriceSaleCodeBoc, 'base64'))[0] -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale/NftFixpriceSale.spec.ts: -------------------------------------------------------------------------------- 1 | import {NftFixpriceSaleLocal} from "./NftFixpriceSaleLocal"; 2 | import {NftFixPriceSaleData} from "./NftFixPriceSale.data"; 3 | import {toNano} from "ton"; 4 | import {randomAddress} from "../../utils/randomAddress"; 5 | 6 | let defaultConfig: NftFixPriceSaleData = { 7 | marketplaceAddress: randomAddress(), 8 | nftAddress: randomAddress(), 9 | nftOwnerAddress: randomAddress(), 10 | fullPrice: toNano(1), 11 | marketplaceFee: toNano(1), 12 | marketplaceFeeAddress: randomAddress(), 13 | royaltyAmount: toNano(1), 14 | royaltyAddress: randomAddress(), 15 | } 16 | 17 | describe('nft marketplace smc', () => { 18 | it('should return sale info', async () => { 19 | let sale = await NftFixpriceSaleLocal.createFromConfig(defaultConfig) 20 | 21 | let res = await sale.getSaleData() 22 | 23 | expect(res.marketplaceAddress.toFriendly()).toEqual(defaultConfig.marketplaceAddress.toFriendly()) 24 | expect(res.nftAddress.toFriendly()).toEqual(defaultConfig.nftAddress.toFriendly()) 25 | expect(res.nftOwnerAddress.toFriendly()).toEqual(defaultConfig.nftOwnerAddress!.toFriendly()) 26 | expect(res.marketplaceFeeAddress.toFriendly()).toEqual(defaultConfig.marketplaceFeeAddress.toFriendly()) 27 | expect(res.royaltyAddress.toFriendly()).toEqual(defaultConfig.royaltyAddress.toFriendly()) 28 | expect(res.fullPrice.eq(defaultConfig.fullPrice)).toBe(true) 29 | expect(res.fullPrice.eq(defaultConfig.marketplaceFee)).toBe(true) 30 | expect(res.fullPrice.eq(defaultConfig.royaltyAmount)).toBe(true) 31 | }) 32 | }) -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale/NftFixpriceSaleLocal.ts: -------------------------------------------------------------------------------- 1 | import {SmartContract} from "ton-contract-executor"; 2 | import {Address, Cell, contractAddress, Slice} from "ton"; 3 | import {NftFixPriceSaleSource} from "./NftFixPriceSale.source"; 4 | import {buildNftFixPriceSaleDataCell, NftFixPriceSaleData} from "./NftFixPriceSale.data"; 5 | import BN from "bn.js"; 6 | import {compileFunc} from "../../utils/compileFunc"; 7 | 8 | export class NftFixpriceSaleLocal { 9 | private constructor( 10 | public readonly contract: SmartContract, 11 | public readonly address: Address 12 | ) { 13 | 14 | } 15 | 16 | async getSaleData() { 17 | let res = await this.contract.invokeGetMethod('get_sale_data', []) 18 | 19 | if (res.exit_code !== 0) { 20 | throw new Error(`Unable to invoke get_sale_data on sale contract`) 21 | } 22 | 23 | let [ 24 | marketplaceAddressSlice, 25 | nftAddressSlice, 26 | nftOwnerAddressSlice, 27 | fullPrice, 28 | marketplaceFeeAddressSlice, 29 | marketplaceFee, 30 | royaltyAddressSlice, 31 | royaltyAmount, 32 | ] = res.result as [Slice, Slice, Slice, BN, Slice, BN, Slice, BN, BN] 33 | 34 | return { 35 | marketplaceAddress: marketplaceAddressSlice.readAddress()!, 36 | nftAddress: nftAddressSlice.readAddress()!, 37 | nftOwnerAddress: nftOwnerAddressSlice.readAddress()!, 38 | fullPrice, 39 | marketplaceFeeAddress: marketplaceFeeAddressSlice.readAddress()!, 40 | marketplaceFee, 41 | royaltyAddress: royaltyAddressSlice.readAddress()!, 42 | royaltyAmount, 43 | } 44 | } 45 | 46 | static async createFromConfig(config: NftFixPriceSaleData) { 47 | let code = await compileFunc(NftFixPriceSaleSource) 48 | 49 | let data = buildNftFixPriceSaleDataCell(config) 50 | let contract = await SmartContract.fromCell(code.cell, data) 51 | 52 | let address = contractAddress({ 53 | workchain: 0, 54 | initialData: contract.dataCell, 55 | initialCode: contract.codeCell 56 | }) 57 | 58 | contract.setC7Config({ 59 | myself: address 60 | }) 61 | 62 | return new NftFixpriceSaleLocal(contract, address) 63 | } 64 | 65 | static async create(config: { code: Cell, data: Cell, address: Address }) { 66 | let contract = await SmartContract.fromCell(config.code, config.data) 67 | contract.setC7Config({ 68 | myself: config.address 69 | }) 70 | return new NftFixpriceSaleLocal(contract, config.address) 71 | } 72 | } -------------------------------------------------------------------------------- /packages/contracts/nft-fixprice-sale/isNftFixPriceSaleContract.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell, Slice} from "ton"; 2 | import {NftFixPriceSaleCodeCell} from "./NftFixPriceSale.source"; 3 | import {SmartContract} from "ton-contract-executor"; 4 | import BN from "bn.js"; 5 | 6 | export async function isNftFixPriceSaleContract(address: Address, codeCell: Cell, dataCell: Cell) { 7 | // Most common case for standard contracts 8 | if (NftFixPriceSaleCodeCell.hash().equals(codeCell.hash())) { 9 | return true 10 | } 11 | 12 | try { 13 | let contract = await SmartContract.fromCell(codeCell, dataCell) 14 | 15 | // (int, int, slice, slice, cell) get_nft_data() 16 | let res = await contract.invokeGetMethod('get_sale_data', []) 17 | if (res.exit_code !== 0 || res.type !== 'success') { 18 | return false 19 | } 20 | 21 | if (res.result.length !== 5) { 22 | return false 23 | } 24 | 25 | let [ 26 | marketplaceAddressSlice, 27 | nftAddressSlice, 28 | nftOwnerAddressSlice, 29 | fullPrice, 30 | marketplaceFeeAddressSlice, 31 | marketplaceFee, 32 | royaltyAddressSlice, 33 | royaltyAmount, 34 | ] = res.result 35 | 36 | return ( 37 | marketplaceAddressSlice instanceof Slice && 38 | nftAddressSlice instanceof Slice && 39 | nftOwnerAddressSlice instanceof Slice && 40 | fullPrice instanceof BN && 41 | marketplaceFeeAddressSlice instanceof Slice && 42 | marketplaceFee instanceof BN && 43 | royaltyAddressSlice instanceof Slice && 44 | royaltyAmount instanceof BN 45 | ) 46 | } catch (e) { 47 | return false 48 | } 49 | } -------------------------------------------------------------------------------- /packages/contracts/nft-item/NftItem.source.ts: -------------------------------------------------------------------------------- 1 | import {Cell} from "ton"; 2 | import {combineFunc} from "../../utils/combineFunc"; 3 | 4 | export const NftItemSource = combineFunc(__dirname, [ 5 | '../sources/stdlib.fc', 6 | '../sources/op-codes.fc', 7 | '../sources/params.fc', 8 | '../sources/nft-item.fc', 9 | ]) 10 | 11 | export const NftItemCodeBoc = 'te6cckECDQEAAdAAART/APSkE/S88sgLAQIBYgMCAAmhH5/gBQICzgcEAgEgBgUAHQDyMs/WM8WAc8WzMntVIAA7O1E0NM/+kAg10nCAJp/AfpA1DAQJBAj4DBwWW1tgAgEgCQgAET6RDBwuvLhTYALXDIhxwCSXwPg0NMDAXGwkl8D4PpA+kAx+gAxcdch+gAx+gAw8AIEs44UMGwiNFIyxwXy4ZUB+kDUMBAj8APgBtMf0z+CEF/MPRRSMLqOhzIQN14yQBPgMDQ0NTWCEC/LJqISuuMCXwSED/LwgCwoAcnCCEIt3FzUFyMv/UATPFhAkgEBwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7AAH2UTXHBfLhkfpAIfAB+kDSADH6AIIK+vCAG6EhlFMVoKHeItcLAcMAIJIGoZE24iDC//LhkiGOPoIQBRONkchQCc8WUAvPFnEkSRRURqBwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7ABBHlBAqN1viDACCAo41JvABghDVMnbbEDdEAG1xcIAQyMsFUAfPFlAF+gIVy2oSyx/LPyJus5RYzxcBkTLiAckB+wCTMDI04lUC8ANqhGIu' 12 | 13 | export const NftItemCodeCell = Cell.fromBoc(Buffer.from(NftItemCodeBoc, 'base64'))[0] 14 | 15 | export const NftSingleSource = combineFunc(__dirname, [ 16 | '../sources/stdlib.fc', 17 | '../sources/op-codes.fc', 18 | '../sources/params.fc', 19 | '../sources/nft-single.fc', 20 | ]) 21 | 22 | export const NftSingleCodeBoc = 'te6cckECFQEAAwoAART/APSkE/S88sgLAQIBYgcCAgEgBAMAI7x+f4ARgYuGRlgOS/uAFoICHAIBWAYFABG0Dp4AQgRr4HAAHbXa/gBNhjoaYfph/0gGEAICzgsIAgEgCgkAGzIUATPFljPFszMye1UgABU7UTQ+kD6QNTUMIAIBIA0MABE+kQwcLry4U2AEuQyIccAkl8D4NDTAwFxsJJfA+D6QPpAMfoAMXHXIfoAMfoAMPACBtMf0z+CEF/MPRRSMLqOhzIQRxA2QBXgghAvyyaiUjC64wKCEGk9OVBSMLrjAoIQHARBKlIwuoBMSEQ4BXI6HMhBHEDZAFeAxMjQ1NYIQGgudURK6n1ETxwXy4ZoB1NQwECPwA+BfBIQP8vAPAfZRNscF8uGR+kAh8AH6QNIAMfoAggr68IAboSGUUxWgod4i1wsBwwAgkgahkTbiIML/8uGSIY4+ghBRGkRjyFAKzxZQC88WcSRKFFRGsHCAEMjLBVAHzxZQBfoCFctqEssfyz8ibrOUWM8XAZEy4gHJAfsAEFeUECo4W+IQAIICjjUm8AGCENUydtsQN0UAbXFwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7AJMwMzTiVQLwAwBUFl8GMwHQEoIQqMsArXCAEMjLBVAFzxYk+gIUy2oTyx/LPwHPFsmAQPsAAIYWXwZsInDIywHJcIIQi3cXNSHIy/8D0BPPFhOAQHCAEMjLBVAHzxZQBfoCFctqEssfyz8ibrOUWM8XAZEy4gHJAfsAAfZRN8cF8uGR+kAh8AH6QNIAMfoAggr68IAboSGUUxWgod4i1wsBwwAgkgahkTbiIMIA8uGSIY4+ghAFE42RyFALzxZQC88WcSRLFFRGwHCAEMjLBVAHzxZQBfoCFctqEssfyz8ibrOUWM8XAZEy4gHJAfsAEGeUECo5W+IUAIICjjUm8AGCENUydtsQN0YAbXFwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7AJMwNDTiVQLwA+GNLv4=' 23 | 24 | export const NftSingleCodeCell = Cell.fromBoc(Buffer.from(NftSingleCodeBoc, 'base64'))[0] -------------------------------------------------------------------------------- /packages/contracts/nft-item/isNftItemContract.spec.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell, TonClient} from "ton"; 2 | import {isNftItemContract} from "./isNftItemContract"; 3 | 4 | describe('nft detector', () => { 5 | it('should detect nft', async () => { 6 | let client = new TonClient({ 7 | endpoint: 'https://toncenter.com/api/v2/jsonRPC', 8 | apiKey: 'd501ac81fedb6859076f536cbdadeaa2759e0ddd19d42e8d7ef7f0fd3cdeb062' 9 | }) 10 | 11 | let address = Address.parse('EQCNtVgiIbVGXlBClDApbQjTUC_Zm_V8IhgUcwz8OS08-NgX') 12 | let res = await client.getContractState(address) 13 | 14 | let code = Cell.fromBoc(res.code!)[0] 15 | let data = Cell.fromBoc(res.data!)[0] 16 | 17 | let isCollection = await isNftItemContract(address, code, data) 18 | 19 | expect(isCollection).toBe(true) 20 | }) 21 | }) -------------------------------------------------------------------------------- /packages/contracts/nft-item/isNftItemContract.ts: -------------------------------------------------------------------------------- 1 | import {Address, Cell, Slice} from "ton"; 2 | import {SmartContract} from "ton-contract-executor"; 3 | import BN from "bn.js"; 4 | import {NftItemCodeCell, NftSingleCodeCell} from "./NftItem.source"; 5 | 6 | const NftItemCodeCellHash = NftItemCodeCell.hash() 7 | const NftSingleCodeCellHash = NftSingleCodeCell.hash() 8 | 9 | export async function isNftItemContract(address: Address, codeCell: Cell, dataCell: Cell) { 10 | let contract = await SmartContract.fromCell(codeCell, dataCell) 11 | return await isNftItemContractModern(contract) 12 | } 13 | 14 | export async function isNftItemContractModern(contract: SmartContract) { 15 | let codeHash = contract.codeCell.hash() 16 | 17 | // Most common case for standard contracts 18 | if (NftItemCodeCellHash.equals(codeHash)) { 19 | return true 20 | } 21 | if (NftSingleCodeCellHash.equals(codeHash)) { 22 | return true 23 | } 24 | 25 | try { 26 | // (int, int, slice, slice, cell) get_nft_data() 27 | let res = await contract.invokeGetMethod('get_nft_data', []) 28 | if (res.exit_code !== 0 || res.type !== 'success') { 29 | return false 30 | } 31 | 32 | // Actually it should be strictly 5, but some NFT's return extra info 33 | if (res.result.length < 5) { 34 | return false 35 | } 36 | 37 | let [initialized, index, collectionAddress, ownerAddress, content] = res.result 38 | 39 | if (!(initialized instanceof BN)) { 40 | return false 41 | } 42 | if (!(index instanceof BN)) { 43 | return false 44 | } 45 | if (!(collectionAddress instanceof Slice)) { 46 | return false 47 | } 48 | if (!(ownerAddress instanceof Slice)) { 49 | return false 50 | } 51 | if (!(content instanceof Cell)) { 52 | return false 53 | } 54 | 55 | return true 56 | } catch (e) { 57 | return false 58 | } 59 | } -------------------------------------------------------------------------------- /packages/contracts/nft-marketplace/NftMarketplace.data.ts: -------------------------------------------------------------------------------- 1 | import {Cell, contractAddress, StateInit} from "ton"; 2 | import {KeyPair, sign} from "ton-crypto"; 3 | import {NftMarketplaceCodeCell} from "./NftMarketplace.source"; 4 | 5 | export type NftMarketplaceData = { 6 | seqno: number 7 | subwallet: number 8 | publicKey: Buffer 9 | } 10 | 11 | export function buildNftMarketplaceDataCell(data: NftMarketplaceData) { 12 | let dataCell = new Cell() 13 | 14 | dataCell.bits.writeUint(data.seqno, 32) 15 | dataCell.bits.writeUint(data.subwallet, 32) 16 | dataCell.bits.writeBuffer(data.publicKey) 17 | 18 | return dataCell 19 | } 20 | 21 | export async function buildNftMarketplaceStateInit(keyPair: KeyPair) { 22 | let data: NftMarketplaceData = { 23 | seqno: 0, 24 | subwallet: 0, 25 | publicKey: keyPair.publicKey 26 | } 27 | let dataCell = buildNftMarketplaceDataCell(data) 28 | let stateInit = new StateInit({ 29 | code: NftMarketplaceCodeCell, 30 | data: dataCell 31 | }) 32 | let address = contractAddress({workchain: 0, initialCode: NftMarketplaceCodeCell, initialData: dataCell}) 33 | 34 | return { 35 | address, 36 | stateInit 37 | } 38 | } 39 | 40 | export const OperationCodes = { 41 | DeploySale: 1, 42 | } 43 | 44 | export function buildSignature(params: { keyPair: KeyPair, saleStateInit: Cell, saleMessageBody: Cell }) { 45 | let bodyCell = new Cell() 46 | bodyCell.refs.push(params.saleStateInit) 47 | bodyCell.refs.push(params.saleMessageBody) 48 | 49 | return sign(bodyCell.hash(), params.keyPair.secretKey) 50 | } 51 | 52 | export const Queries = { 53 | signedDeploySale: (params: { keyPair: KeyPair, saleStateInit: Cell, saleMessageBody: Cell }) => { 54 | let signature = buildSignature(params) 55 | 56 | let msgBody = new Cell() 57 | msgBody.bits.writeUint(OperationCodes.DeploySale, 32) 58 | msgBody.bits.writeBuffer(signature) 59 | msgBody.refs.push(params.saleStateInit) 60 | msgBody.refs.push(params.saleMessageBody) 61 | 62 | return msgBody 63 | } 64 | } -------------------------------------------------------------------------------- /packages/contracts/nft-marketplace/NftMarketplace.source.ts: -------------------------------------------------------------------------------- 1 | import {Cell} from "ton"; 2 | import {combineFunc} from "../../utils/combineFunc"; 3 | 4 | export const NftMarketplaceSource = combineFunc(__dirname, [ 5 | '../sources/stdlib.fc', 6 | '../sources/op-codes.fc', 7 | '../sources/nft-marketplace-v2.fc', 8 | ]) 9 | 10 | export const NftMarketplaceCodeBoc = 'te6cckEBDAEA7wABFP8A9KQT9LzyyAsBAgEgAwIAePKDCNcYINMf0x/THwL4I7vyY/ABUTK68qFRRLryogT5AVQQVfkQ8qP4AJMg10qW0wfUAvsA6DABpALwAgIBSAcEAgFIBgUAEbjJftRNDXCx+AAXuznO1E0NM/MdcL/4AgLOCQgAF0AsjLH8sfy//J7VSAIBIAsKABU7UTQ0x/TH9P/MIACpGwiIMcAkVvgAdDTAzBxsJEw4PABbCEB0x8BwAGONIMI1xgg+QFAA/kQ8qPU1DAh+QBwyMoHy//J0Hd0gBjIywXLAljPFnD6AstrEszMyYBA+wDgW4NC26jQ=' 11 | 12 | export const NftMarketplaceCodeCell = Cell.fromBoc(Buffer.from(NftMarketplaceCodeBoc, 'base64'))[0] -------------------------------------------------------------------------------- /packages/contracts/nft-marketplace/NftMarketplace.spec.ts: -------------------------------------------------------------------------------- 1 | import {NftMarketplaceData} from "./NftMarketplace.data"; 2 | import {randomAddress} from "../../utils/randomAddress"; 3 | import {Cell, CellMessage, CommonMessageInfo, InternalMessage, StateInit, toNano} from "ton"; 4 | import {KeyPair} from "ton-crypto"; 5 | import {NftMarketplaceLocal} from "./NftMarketplaceLocal"; 6 | import {randomKeyPair} from "../../utils/randomKeyPair"; 7 | import {SendMsgAction} from "ton-contract-executor"; 8 | 9 | let defaultConfig: NftMarketplaceData 10 | let keyPair: KeyPair 11 | 12 | describe('nft marketplace smc', () => { 13 | beforeAll(async () => { 14 | keyPair = await randomKeyPair() 15 | 16 | defaultConfig = { 17 | seqno: 77, 18 | subwallet: 88, 19 | publicKey: keyPair.publicKey 20 | } 21 | }) 22 | 23 | it('should return seqno', async () => { 24 | let market = await NftMarketplaceLocal.createFromConfig(defaultConfig) 25 | let res = await market.getSeqno() 26 | expect(res.eqn(defaultConfig.seqno)).toBe(true) 27 | }) 28 | 29 | it('should return public key', async () => { 30 | let market = await NftMarketplaceLocal.createFromConfig(defaultConfig) 31 | let res = await market.getPublicKey() 32 | expect(res.equals(defaultConfig.publicKey)).toBe(true) 33 | }) 34 | 35 | it('should deploy signed nft sale', async () => { 36 | let market = await NftMarketplaceLocal.createFromConfig(defaultConfig) 37 | let sender = randomAddress() 38 | 39 | let saleCode = new Cell() 40 | saleCode.bits.writeUint(123, 32) 41 | let saleData = new Cell() 42 | saleData.bits.writeUint(1234, 32) 43 | let saleStateInit = new StateInit({ 44 | code: saleCode, 45 | data: saleData 46 | }) 47 | 48 | let saleStateInitCell = new Cell() 49 | saleStateInit.writeTo(saleStateInitCell) 50 | 51 | let saleMessageBody = new Cell() 52 | saleMessageBody.bits.writeUint(12345, 32) 53 | 54 | let res = await market.sendDeploySaleSigned(sender, { 55 | saleStateInit: saleStateInitCell, 56 | saleMessageBody: saleMessageBody, 57 | keyPair 58 | }) 59 | 60 | if (res.type !== 'success') { 61 | throw new Error() 62 | } 63 | let [initMessage] = res.actionList as [SendMsgAction] 64 | 65 | expect(initMessage.message.init!.code!.toString()).toEqual(saleCode.toString()) 66 | expect(initMessage.message.init!.data!.toString()).toEqual(saleData.toString()) 67 | expect(initMessage.message.body.toString()).toEqual(saleMessageBody.toString()) 68 | expect(initMessage.mode).toEqual(64) // transfer all funds from message 69 | }) 70 | 71 | it('should accept coins if empty message was sent', async () => { 72 | let market = await NftMarketplaceLocal.createFromConfig(defaultConfig) 73 | let res = await market.contract.sendInternalMessage(new InternalMessage({ 74 | to: market.address, 75 | from: randomAddress(), 76 | value: toNano(1), 77 | bounce: false, 78 | body: new CommonMessageInfo({ 79 | body: new CellMessage(new Cell()) 80 | }) 81 | })) 82 | if (res.type !== 'success') { 83 | throw new Error() 84 | } 85 | 86 | expect(res.exit_code).toEqual(0) 87 | expect(res.actionList).toHaveLength(0) 88 | }) 89 | }) -------------------------------------------------------------------------------- /packages/contracts/nft-marketplace/NftMarketplaceLocal.ts: -------------------------------------------------------------------------------- 1 | import {SmartContract} from "ton-contract-executor"; 2 | import {Address, Cell, CellMessage, CommonMessageInfo, contractAddress, InternalMessage, toNano} from "ton"; 3 | import { 4 | buildNftMarketplaceDataCell, 5 | buildNftMarketplaceStateInit, 6 | NftMarketplaceData, OperationCodes, 7 | Queries 8 | } from "./NftMarketplace.data"; 9 | import {NftMarketplaceSource} from "./NftMarketplace.source"; 10 | import BN from "bn.js"; 11 | import {KeyPair} from "ton-crypto"; 12 | import {compileFunc} from "../../utils/compileFunc"; 13 | 14 | export class NftMarketplaceLocal { 15 | private constructor( 16 | public readonly contract: SmartContract, 17 | public readonly address: Address 18 | ) { 19 | 20 | } 21 | 22 | static operationCodes = OperationCodes 23 | static queries = Queries 24 | 25 | static async buildStateInit(keyPair: KeyPair) { 26 | return buildNftMarketplaceStateInit(keyPair) 27 | } 28 | 29 | async getSeqno() { 30 | let res = await this.contract.invokeGetMethod('seqno', []) 31 | return res.result[0] as BN 32 | } 33 | 34 | async getPublicKey() { 35 | let res = await this.contract.invokeGetMethod('get_public_key', []) 36 | return (res.result[0] as BN).toBuffer() 37 | } 38 | 39 | async sendDeploySaleSigned(from: Address, saleConf: { keyPair: KeyPair, saleStateInit: Cell, saleMessageBody: Cell }) { 40 | let request = Queries.signedDeploySale(saleConf) 41 | 42 | return await this.contract.sendInternalMessage(new InternalMessage({ 43 | to: this.address, 44 | from: from, 45 | value: toNano(1), 46 | bounce: false, 47 | body: new CommonMessageInfo({ 48 | body: new CellMessage(request) 49 | }) 50 | })) 51 | } 52 | 53 | static async createFromConfig(config: NftMarketplaceData) { 54 | let code = await compileFunc(NftMarketplaceSource) 55 | 56 | let data = buildNftMarketplaceDataCell(config) 57 | let contract = await SmartContract.fromCell(code.cell, data) 58 | 59 | let address = contractAddress({ 60 | workchain: 0, 61 | initialData: contract.dataCell, 62 | initialCode: contract.codeCell 63 | }) 64 | 65 | contract.setC7Config({ 66 | myself: address 67 | }) 68 | 69 | return new NftMarketplaceLocal(contract, address) 70 | } 71 | 72 | static async create(config: { code: Cell, data: Cell, address: Address }) { 73 | let contract = await SmartContract.fromCell(config.code, config.data) 74 | contract.setC7Config({ 75 | myself: config.address 76 | }) 77 | return new NftMarketplaceLocal(contract, config.address) 78 | } 79 | } -------------------------------------------------------------------------------- /packages/contracts/nft-offer/NftOffer.source.ts: -------------------------------------------------------------------------------- 1 | import { Cell } from 'ton' 2 | import { combineFunc } from "../../utils/combineFunc"; 3 | 4 | export const NftOfferSource = () => { 5 | return combineFunc(__dirname, ['../sources/stdlib.fc', '../sources/op-codes.fc', '../sources/nft-offer.fc']) 6 | } 7 | 8 | /** 9 | * @deprecated use NftOfferV1R3CodeBoc 10 | */ 11 | const NftOfferCodeBoc = 12 | 'te6cckECFgEABEkAART/APSkE/S88sgLAQIBIAQCAVby7UTQ0wDTH9Mf+kD6QPpA+gDU0wAwMAfAAfLRlPgjJb7jAl8IggD//vLwAwDOB9MfgQ+jAsMAEvLygQ+kIddKwwDy8oEPpSHXSYEB9Lzy8vgAgggPQkBw+wJwIIAQyMsFJM8WIfoCy2rLHwHPFsmDBvsAcQdVBXAIyMsAF8sfFcsfUAPPFgHPFgHPFgH6AszLAMntVAIBSAYFAIOhRh/aiaGmAaY/pj/0gfSB9IH0AammAGBhofSBpj+mP/SBpj+mPmCo7CHgBqjuqeAGpQVCA0MEMJ6MjIqkHYACq4ECAswLBwP322ERFofSBpj+mP/SBpj+mPmBSs+AGqJCH4Aam4UJHQxbKDk3szS6QTrLgQQAhkZYKoAueLKAH9AQnltWWPgOeLZLj9gBFhABFrpOEBWEk2EPGGkGEASK3xhrgQQQgv5h6KZGWPieWfk2eLKAHni2UAQQRMS0B9AWUAZLjAoJCAB4gBjIywUmzxZw+gLLasyCCA9CQHD7AsmDBvsAcVVgcAjIywAXyx8Vyx9QA88WAc8WAc8WAfoCzMsAye1UAEyLlPZmZlciBmZWWHAggBDIywVQBc8WUAP6AhPLassfAc8WyXH7AABYi+T2ZmZXIgcm95YWxpZXOBNwIIAQyMsFUAXPFlAD+gITy2rLHwHPFslx+wACAUgPDAIBIA4NAB0IMAAk18DcOBZ8AIB8AGAAESCEDuaygCphIAIBIBEQABMghA7msoAAamEgAfcAdDTAwFxsJJfBOD6QDDtRNDTANMf0x/6QPpA+kD6ANTTADDAAY4lMTc3OFUzEnAIyMsAF8sfFcsfUAPPFgHPFgHPFgH6AszLAMntVOB/KscBwACOGjAJ0x8hwACLZjYW5jZWyFIgxwWwknMy3lCq3iCBAiu6KcABsFOmgEgLQxwWwnhCsXwzUMNDTB9QwAfsA4IIQBRONkVIQuuMCPCfAAfLRlCvAAFOTxwWwjis4ODlQdqAQN0ZQRAMCcAjIywAXyx8Vyx9QA88WAc8WAc8WAfoCzMsAye1U4Dc5CcAD4wJfCYQP8vAVEwGsU1jHBVNixwWx8uHKgggPQkBw+wJRUccFjhQ1cIAQyMsFKM8WIfoCy2rJgwb7AOMNcUcXUGYFBANwCMjLABfLHxXLH1ADzxYBzxYBzxYB+gLMywDJ7VQUALYF+gAhghAdzWUAvJeCEB3NZQAy3o0EE9mZmVyIGNhbmNlbCBmZWWBURzNwIIAQyMsFUAXPFlAD+gITy2rLHwHPFslx+wDUMHGAEMjLBSnPFnD6AstqzMmDBvsAAMYwCdM/+kAwU5THBQnAABmwK4IQO5rKAL6wnjgQWhBJEDhHFQNEZPAIjjg5XwYzM3AgghBfzD0UyMsfE8s/I88WUAPPFsoAIfoCygDJcYAYyMsFUAPPFnD6AhLLaszJgED7AOK1Lpfy' 13 | 14 | export const NftOfferCodeCell = Cell.fromBoc(Buffer.from(NftOfferCodeBoc, 'base64'))[0] 15 | 16 | export const NftOfferV1R3CodeBoc = 'te6ccgECGgEABSwAART/APSkE/S88sgLAQIBIAIDAgFIBAUBWvLtRNDTANMf0x/TH/pA+kD6QPoA1NMAMDAIwAHy0ZT4Iya+4wJfCYIA//7y8BkCAswGBwIBIBcYAgEgCAkB97LYREWh9IGmP6Y/9IGmP6Y+YFKz4AaokIfgBqbhQkdDFsoOTezNLpBOsuBBACGRlgqgC54soAf0BCeW1ZY+A54tkuP2AEWEAEWuk4QFYRxLFupN7ywtjo8wJuBBACGRlgqgC54soAf0BCeW1ZY+A54tkuP2ASTYQ8RBhAEVAgEgCgsAIdKiIieAGqIYH4AaiQ0JFQiUAgEgDA0CASATFAH1AHQ0wMBcbCSXwTg+kAw7UTQ0wDTH9Mf0x/6QPpA+kD6ANTTADDAAY5MOTk6JtD6QNMf0x/6QNMf0x8wMjQQS/AEbCGCCExLQLny0ZcQSBA3RhRAMwVwCcjLABjLHxbLHxTLH1jPFgHPFgHPFgH6AszLAMntVOB/K8cBgDgATIIQO5rKAAGphIAL8wACOGjAK0x8hwACLZjYW5jZWyFIgxwWwknMy3lC73iCBAiu6KsABsFO2xwWwjjQQal8KMzHUMNDTByLDAI4UgQJY+CNTQaG8BPgjAqC5E7Dy0ZaRMuIggCCw8tGVAdQwAfsA4IIQBRONkVIQuuMCPSjAAfLRlCzAAFOjxwWwDxAAxjA1CdM/+kAwU5PHBQnAABmwK4IQBfXhAL6wnTgQWhBJSHZeIkMA8AmOODlfBjMzcCCCEF/MPRTIyx8Tyz8jzxZQA88WygAh+gLKAMlxgBjIywVQA88WcPoCEstqzMmAQPsA4gGCjjA5OTpQh6AQSBA3RlAQNEMAcAnIywAYyx8Wyx8Uyx9YzxYBzxYBzxYB+gLMywDJ7VTgNTc5CcAD4wJfCYQP8vARAbJTV8cFU2HHBbHy4cqCCA9CQHD7AlFVxwWOFDVwgBDIywUnzxYh+gLLasmDBvsA4w1xRldwBVBEQxNwCcjLABjLHxbLHxTLH1jPFgHPFgHPFgH6AszLAMntVBIAtgX6ACGCEB3NZQC8l4IQHc1lADLejQQT2ZmZXIgY2FuY2VsIGZlZYFRGM3AggBDIywVQBc8WUAP6AhPLassfAc8WyXH7ANQwcYAQyMsFKM8WcPoCy2rMyYMG+wAAESCEDuaygCphIAAdCDAAJNfA3DgWfACAfABgAe6OLIv01hcmtldHBsYWNlIGZlZYcCCAEMjLBVAFzxZQA/oCE8tqyx8BzxbJcfsAkVvicCCCEF/MPRTIyx8Tyz8mzxZQA88WygCCCJiWgPoCygDJcYAYyMsFJs8WcPoCy2rMgggPQkBw+wLJgwb7AHEHBgX4IwVVIRYAQHAJyMsAGMsfFssfFMsfWM8WAc8WAc8WAfoCzMsAye1UAJm9lx9qJoaYBpj+mP6Y/9IH0gfSB9AGppgBhgAPlozGh9IGmP6Y/9IGmP6Y+YKjsKKbH4AjYQwQQmJaApCFz5aMzBDCejIyKpB+AAquhAClvRh/aiaGmAaY/pj+mP/SB9IH0gfQBqaYAYGwLgAPlozAJofSBpj+mP/SBpj+mPmCo8iimx+AI2EMEEJiWgKQhc+WjMwQwnoyMiqQdgAIaGBaqwQA0gjTH4EPowLDABLy8oEPpCHXSsMA8vKBD6Uh10mBAfS88vL4AIIID0JAcPsCcCCAEMjLBSTPFiH6Astqyx8BzxbJgwb7AHEIVQZwCcjLABjLHxbLHxTLH1jPFgHPFgHPFgH6AszLAMntVA==' 17 | -------------------------------------------------------------------------------- /packages/contracts/nft-offer/NftOfferData.ts: -------------------------------------------------------------------------------- 1 | import { Address, Builder, Cell, contractAddress, StateInit } from 'ton' 2 | import BN from 'bn.js' 3 | import { NftOfferCodeCell } from './NftOffer.source' 4 | 5 | export type NftOfferData = { 6 | isComplete: boolean 7 | createdAt: number 8 | finishAt: number 9 | marketplaceAddress: Address 10 | nftAddress: Address 11 | offerOwnerAddress: Address 12 | fullPrice: BN 13 | marketplaceFeeAddress: Address 14 | royaltyAddress: Address 15 | marketplaceFactor: number 16 | marketplaceBase: number 17 | royaltyFactor: number 18 | royaltyBase: number 19 | canDeploy?: number // for tests only 20 | } 21 | 22 | export function buildNftOfferDataCell(data: NftOfferData) { 23 | const feesCell = new Cell() 24 | 25 | feesCell.bits.writeAddress(data.marketplaceFeeAddress) 26 | feesCell.bits.writeUint(data.marketplaceFactor, 32) 27 | feesCell.bits.writeUint(data.marketplaceBase, 32) 28 | feesCell.bits.writeAddress(data.royaltyAddress) 29 | feesCell.bits.writeUint(data.royaltyFactor, 32) 30 | feesCell.bits.writeUint(data.royaltyBase, 32) 31 | 32 | const dataCell = new Cell() 33 | 34 | dataCell.bits.writeUint(data.isComplete ? 1 : 0, 1) 35 | dataCell.bits.writeUint(data.createdAt, 32) 36 | dataCell.bits.writeUint(data.finishAt, 32) 37 | dataCell.bits.writeAddress(data.marketplaceAddress) 38 | dataCell.bits.writeAddress(data.nftAddress) 39 | dataCell.bits.writeAddress(data.offerOwnerAddress) 40 | dataCell.bits.writeCoins(data.fullPrice) // fullPrice 41 | dataCell.refs.push(feesCell) 42 | dataCell.bits.writeUint(1, 1) // can_deploy 43 | 44 | return dataCell 45 | } 46 | 47 | export function buildNftOfferStateInit(data: NftOfferData) { 48 | const dataCell = buildNftOfferDataCell({ 49 | ...data, 50 | isComplete: false, 51 | }) 52 | 53 | const stateInit = new StateInit({ 54 | code: NftOfferCodeCell, 55 | data: dataCell, 56 | }) 57 | const address = contractAddress({ workchain: 0, initialCode: NftOfferCodeCell, initialData: dataCell }) 58 | 59 | return { 60 | address, 61 | stateInit, 62 | } 63 | } 64 | 65 | export const OperationCodes = { 66 | AcceptCoins: 0, 67 | } 68 | 69 | export const Queries = { 70 | deployOfferPayload: (name: string) => { 71 | const b = new Builder() 72 | b.storeUint(0, 32) 73 | const m = Buffer.from(name.substring(0, 121), 'utf-8') 74 | b.storeBuffer(m.slice(0, 121)) 75 | return b.endCell() 76 | }, 77 | cancelOfferInternalMessage: (params: { message?: string } = {}) => { 78 | const nextPayload = new Cell() 79 | if (params.message) { 80 | nextPayload.bits.writeUint(0, 32) 81 | const m = Buffer.from(params.message.substring(0, 121), 'utf-8') 82 | nextPayload.bits.writeBuffer(m.slice(0, 121)) 83 | } 84 | const msgBody = new Cell() 85 | msgBody.bits.writeUint(0, 32) 86 | msgBody.bits.writeBuffer(Buffer.from('cancel')) 87 | msgBody.refs.push(nextPayload) 88 | return msgBody 89 | }, 90 | cancelOfferByMarketplaceInternalMessage: (params: { amount: BN; message?: string }) => { 91 | const nextPayload = new Cell() 92 | if (params.message) { 93 | nextPayload.bits.writeUint(0, 32) 94 | const m = Buffer.from(params.message.substring(0, 121), 'utf-8') 95 | nextPayload.bits.writeBuffer(m.slice(0, 121)) 96 | } 97 | const msgBody = new Cell() 98 | msgBody.bits.writeUint(3, 32) 99 | msgBody.bits.writeCoins(params.amount) 100 | msgBody.refs.push(nextPayload) 101 | return msgBody 102 | }, 103 | cancelOfferExternalMessage: (params: { message?: string } = {}) => { 104 | const msgBody = new Cell() 105 | msgBody.bits.writeUint(0, 32) 106 | msgBody.bits.writeBuffer(Buffer.from((params.message || '').substring(0, 62)).slice(0, 62)) 107 | return msgBody 108 | }, 109 | } 110 | 111 | 112 | export type NftOfferDataV1R3 = NftOfferData & { 113 | swapAt?: number 114 | } 115 | 116 | export function buildNftOfferV1R3DataCell(data: NftOfferDataV1R3) { 117 | const feesCell = new Cell() 118 | 119 | feesCell.bits.writeAddress(data.marketplaceFeeAddress) 120 | feesCell.bits.writeUint(data.marketplaceFactor, 32) 121 | feesCell.bits.writeUint(data.marketplaceBase, 32) 122 | feesCell.bits.writeAddress(data.royaltyAddress) 123 | feesCell.bits.writeUint(data.royaltyFactor, 32) 124 | feesCell.bits.writeUint(data.royaltyBase, 32) 125 | 126 | const dataCell = new Cell() 127 | 128 | dataCell.bits.writeUint(data.isComplete ? 1 : 0, 1) 129 | dataCell.bits.writeUint(data.createdAt, 32) 130 | dataCell.bits.writeUint(data.finishAt, 32) 131 | dataCell.bits.writeUint(data.swapAt || 0, 32) 132 | dataCell.bits.writeAddress(data.marketplaceAddress) 133 | dataCell.bits.writeAddress(data.nftAddress) 134 | dataCell.bits.writeAddress(data.offerOwnerAddress) 135 | dataCell.bits.writeCoins(data.fullPrice) // fullPrice 136 | dataCell.refs.push(feesCell) 137 | dataCell.bits.writeUint(data.canDeploy === 0 || data.canDeploy === 1 ? data.canDeploy : 1, 1) // can_deploy 138 | 139 | return dataCell 140 | } 141 | -------------------------------------------------------------------------------- /packages/contracts/nft-offer/NftOfferLocal.ts: -------------------------------------------------------------------------------- 1 | import { SmartContract } from 'ton-contract-executor' 2 | import { Address, Cell, contractAddress, Slice } from 'ton' 3 | import BN from 'bn.js' 4 | import { buildNftOfferDataCell, NftOfferData, Queries } from './NftOfferData' 5 | import { NftOfferSource } from './NftOffer.source' 6 | import { compileFunc } from "../../utils/compileFunc"; 7 | 8 | 9 | export class NftOfferLocal { 10 | private constructor(public readonly contract: SmartContract, public readonly address: Address, private readonly balance?: BN) {} 11 | 12 | static queries = Queries 13 | 14 | async getOfferData() { 15 | const res = await this.contract.invokeGetMethod('get_offer_data', []) 16 | 17 | if (res.exit_code !== 0) { 18 | throw new Error('Unable to invoke get_offer_data on offer contract') 19 | } 20 | 21 | const [ 22 | offerType, 23 | isComplete, 24 | createdAt, 25 | finishAt, 26 | marketplaceAddressSlice, 27 | nftAddressSlice, 28 | offerOwnerAddressSlice, 29 | fullPrice, 30 | marketplaceFeeAddressSlice, 31 | marketplaceFactor, 32 | marketplaceBase, 33 | royaltyAddressSlice, 34 | royaltyFactor, 35 | royaltyBase, 36 | profitPrice, 37 | ] = res.result as [BN, BN, BN, BN, Slice, Slice, Slice, BN, Slice, BN, BN, Slice, BN, BN, BN] 38 | 39 | if (offerType.toNumber() !== 0x4f46464552) { 40 | throw new Error(`Unknown offer type: ${offerType.toString()}`) 41 | } 42 | 43 | return { 44 | isComplete: isComplete.eqn(-1), 45 | createdAt: createdAt.toNumber(), 46 | finishAt: finishAt.toNumber(), 47 | marketplaceAddress: marketplaceAddressSlice.readAddress()!, 48 | nftAddress: nftAddressSlice.readAddress()!, 49 | offerOwnerAddress: offerOwnerAddressSlice.readAddress()!, 50 | fullPrice, 51 | marketplaceFeeAddress: marketplaceFeeAddressSlice.readAddress()!, 52 | marketplaceFactor, 53 | marketplaceBase, 54 | royaltyAddress: royaltyAddressSlice.readAddress()!, 55 | royaltyFactor, 56 | royaltyBase, 57 | profitPrice, 58 | } 59 | } 60 | 61 | static async createFromConfig(config: NftOfferData) { 62 | const code = await compileFunc(NftOfferSource()) 63 | 64 | const data = buildNftOfferDataCell(config) 65 | const contract = await SmartContract.fromCell(code.cell, data) 66 | 67 | const address = contractAddress({ 68 | workchain: 0, 69 | initialData: contract.dataCell, 70 | initialCode: contract.codeCell, 71 | }) 72 | 73 | contract.setC7Config({ 74 | myself: address, 75 | }) 76 | 77 | return new NftOfferLocal(contract, address) 78 | } 79 | 80 | static async create(config: { code: Cell; data: Cell; address: Address, balance: BN }) { 81 | const contract = await SmartContract.fromCell(config.code, config.data) 82 | contract.setC7Config({ 83 | myself: config.address, 84 | }) 85 | return new NftOfferLocal(contract, config.address, config.balance) 86 | } 87 | 88 | static async createFromContract(contract: SmartContract, address: Address, balance?: BN) { 89 | contract.setC7Config({ 90 | myself: address, 91 | }) 92 | return new NftOfferLocal(contract, address, balance) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/contracts/nft-offer/index-notice-ru.md: -------------------------------------------------------------------------------- 1 | 2 | # Контракт офера 3 | 4 | Этот контракт используется когда покупатель хочет сделать предложение владельцу нфт о покупке. 5 | Покупатель инициирует процесс покупки создавая контракт офера и переводя на него желаемую сумму. 6 | Продавец нфт может согласиться, отказаться от предложения или вовсе его проигнорировать. 7 | Если продавец согласен на продажу он делает трансфер своей нфт на контракт офера, получив нфт контракт отправляет 8 | тоны владельцу нфт а нфт создателю офера. 9 | Контракт офера имеет срок действия после которого он считается не действительным. Это ограничений не жесткое и используется 10 | для того чтобы можно было отменить офер external сообщением. 11 | 12 | Если продавец не согласен принять офер, то маркетплейс должен самостоятельно отменить офер. Для этого маркетплейс в своем UI 13 | должен показать конпку отмены и проверить что ее нажал именно владелец нфт. Чтобы отменить офер, нфт маркетплейс отправляет оферу 14 | сообщение с op-кодом 3. Маркетплейс может забрать до 0.5 TON с контракта для покрытия издержек на отмену офера. 15 | Маркетплейс должен отправлять достаточное кол-во тон для покрытия газа на обмен нфт, рекомендуемое значение для forward_amount 0.5 тон. 16 | Конкретное значение может быть разным для разных нфт. Маркетплейс должен убедиться что нфт способна отправить правильное сообщение 17 | ownership_assigned во время трансфера https://github.com/ton-blockchain/TEPs/blob/master/text/0062-nft-standard.md#1-transfer 18 | 19 | 20 | ### get_offer_data_v2 21 | 22 | 1. int 0x4f46464552 -- всегда такое значение 23 | 2. int is_complete -- -1 означает что офер уже отменен или принят 24 | 3. int created_at unix timestamp даты создания офера, используется чтобы у оферов для одной нфт были разные адреса 25 | 4. int finish_at unix timestamp даты после которой офер считается не действительным и может быть отменен external сообщением 26 | 5. int swap_at unix timestamp даты когда был совершен обмен 27 | 6. slice(MsgAddress) marketplace_address адрес маркетплейса, этот адрес может отменить офер 28 | 7. slice(MsgAddress) nft_address адрес нфт которую ожидает получить офер для обмена 29 | 8. slice(MsgAddress) offer_owner_address адрес создатедя офера, этот адрес получит нфт в случае обмена и может отменить офер 30 | 9. int full_price полная сумма офера, значение этого поля надо сравнивать с балансом аккаунта, допускаются небольшие расхождения из-за платы за хранение 31 | 10. slice(MsgAddress) marketplace_fee_address адрес для комиссии маркетплейса 32 | 11. int marketplace_factor 33 | 12. int marketplace_base процент комиссии маркетплейса выражен двумя цифрами, пример: marketplace_factor = 10 marketplace_base = 100, означает что комиссия маркетплейса 10% 10/100=0.1=10% 34 | 13. slice(MsgAddress) royalty_address адрес роялти коллекции 35 | 14. int royalty_factor 36 | 15. int royalty_base процент роялти коллекции выражен двумя цифрами аналогичко проценту комиссии маркетплейса 37 | 16. int profit_price примерна сумма которую получит пользователь если пример офер, фактическая сумма будет меньше из-за оплаты хранения контракта и трат на газ 38 | 39 | ### Рекомендуемые проверки перед использованием контракта из сети 40 | 41 | - хеш кода контракта должен совпадать с эталонным, нельзя полагаться только на гет методы 42 | - full_price и баланс аккаунта не должны отличатся друг от друга больше чем на 0.05 тон 43 | - метод get_offer_data_v2 вызывается нормально и значение profit_price больше 0.001 тон 44 | - поля marketplace_address nft_address offer_owner_address royalty_address заполнены и содержат не пустые адреса, адрес должен быть валидным MsgAddress workchain 0 45 | -------------------------------------------------------------------------------- /packages/contracts/nft-offer/isNftOfferContract.ts: -------------------------------------------------------------------------------- 1 | import { Address, Cell } from 'ton' 2 | import { NftOfferCodeCell } from './NftOffer.source' 3 | import { SmartContract } from 'ton-contract-executor' 4 | 5 | const NftOfferCodeCellHash = NftOfferCodeCell.hash() 6 | 7 | export async function isNftOfferContract(address: Address, codeCell: Cell, _dataCell: Cell) { 8 | if (NftOfferCodeCellHash.equals(codeCell.hash())) { 9 | return true 10 | } 11 | return false 12 | } 13 | 14 | export async function isNftOfferContractModern(contract: SmartContract) { 15 | if (NftOfferCodeCellHash.equals(contract.codeCell.hash())) { 16 | return true 17 | } 18 | return false 19 | } 20 | -------------------------------------------------------------------------------- /packages/contracts/nft-raffle/raffle.local.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Address, Cell, contractAddress, parseDict, Slice } from 'ton' 3 | import { SmartContract } from 'ton-contract-executor' 4 | import { encodeRaffleStorage, RaffleStorage } from './raffle.storage' 5 | import * as fs from 'fs' 6 | import BN from 'bn.js' 7 | import { compileFunc } from "../../utils/compileFunc"; 8 | import { RaffleSource } from './raffle.source' 9 | 10 | 11 | 12 | type StateResponse = { 13 | state: number, 14 | rightNftsCount: number, 15 | rightNftsReceived: number, 16 | leftNftsCount: number, 17 | leftNftsReceived: number, 18 | leftUser: Address, 19 | rightUser: Address, 20 | superUser: Address, 21 | leftCommission: BN, 22 | rightCommission: BN, 23 | leftCoinsGot: BN, 24 | rightCoinsGot: BN, 25 | nftTransferFee: BN, 26 | nfts: Map | null, 27 | raffledNfts: Map | null 28 | } 29 | 30 | function DictToMapN (slice : Slice): string { 31 | const value = slice.readUint(4).toNumber() 32 | if (value == 0) { 33 | return 'left not received' 34 | } 35 | if (value == 1) { 36 | return 'right not received' 37 | } 38 | if (value == 2) { 39 | return 'left received' 40 | } else { 41 | return 'right received' 42 | } 43 | } 44 | 45 | function DictToMapB (slice: Slice): string { 46 | if(slice.readBit() == false) { 47 | return 'left' 48 | } else { 49 | return 'right' 50 | } 51 | } 52 | 53 | export class RaffleLocal { 54 | private constructor( 55 | public readonly contract: SmartContract, 56 | public readonly address: Address 57 | ) { 58 | 59 | } 60 | 61 | 62 | async getRaffleState (): Promise { 63 | const res = await this.contract.invokeGetMethod('raffle_state', []) 64 | if (res.type !== 'success') { 65 | throw new Error('Can`t run get raffle state') 66 | } 67 | const [ state, rightNftsCount, rightNftsReceived, leftNftsCount, 68 | leftNftsReceived, leftUser, rightUser, superUser, leftCommission, 69 | rightCommission, leftCoinsGot, rightCoinsGot, 70 | nftTransferFee, nfts, raffledNfts ] = res.result as [BN, BN, 71 | BN, BN, 72 | BN, Slice, Slice, Slice, BN, BN, BN, BN, BN, Cell, Cell, Cell] 73 | const nftMap = nfts ? parseDict( 74 | nfts.beginParse(), 75 | 256, 76 | DictToMapN 77 | ) : null 78 | const raffledMap = raffledNfts ? parseDict( 79 | raffledNfts.beginParse(), 80 | 256, 81 | DictToMapB 82 | ) : null 83 | return { 84 | state: state ? state.toNumber() : 0, 85 | rightNftsCount: rightNftsCount.toNumber(), 86 | rightNftsReceived: rightNftsReceived.toNumber(), 87 | leftNftsCount: leftNftsCount.toNumber(), 88 | leftNftsReceived: leftNftsReceived.toNumber(), 89 | leftUser: leftUser.readAddress() as Address, 90 | rightUser: rightUser.readAddress() as Address, 91 | superUser: superUser.readAddress() as Address, 92 | leftCommission: leftCommission, 93 | rightCommission: rightCommission, 94 | leftCoinsGot: leftCoinsGot, 95 | rightCoinsGot: rightCoinsGot, 96 | nftTransferFee: nftTransferFee, 97 | nfts: nftMap, 98 | raffledNfts: raffledMap 99 | } 100 | } 101 | 102 | static async createFromConfig(raffleStorage: RaffleStorage) { 103 | const code = await compileFunc(RaffleSource) 104 | const data = encodeRaffleStorage(raffleStorage) 105 | const smc = await SmartContract.fromCell(code.cell, data) 106 | const address = contractAddress({ 107 | workchain: 0, 108 | initialData: smc.dataCell, 109 | initialCode: smc.codeCell 110 | }) 111 | 112 | smc.setC7Config({ 113 | myself: address 114 | }) 115 | 116 | return new RaffleLocal(smc, address) 117 | } 118 | } -------------------------------------------------------------------------------- /packages/contracts/nft-raffle/raffle.queries.ts: -------------------------------------------------------------------------------- 1 | import { Builder, Address, Cell, Slice, parseDict, toNano } from 'ton' 2 | import BN from 'bn.js' 3 | import { randomAddress } from '../../utils/randomAddress' 4 | 5 | export const OperationCodes = { 6 | ownershipAssigned: 0x05138d91, 7 | cancel: 2001, 8 | addCoins: 2002, 9 | maintain: 2003, 10 | sendAgain: 2004, 11 | } 12 | 13 | export const Queries = { 14 | nftOwnerAssigned: (params: { queryId?: number, prevOwner: Address}) => { 15 | const msgBody = new Cell() 16 | msgBody.bits.writeUint(OperationCodes.ownershipAssigned, 32) 17 | msgBody.bits.writeUint(params.queryId || 0, 64) 18 | msgBody.bits.writeAddress(params.prevOwner) 19 | 20 | return msgBody 21 | }, 22 | cancel: () => { 23 | const msg = new Builder() 24 | .storeUint(OperationCodes.cancel, 32) 25 | return msg.endCell() 26 | }, 27 | addCoins: () => { 28 | const msg = new Builder() 29 | .storeUint(OperationCodes.addCoins, 32) 30 | return msg.endCell() 31 | }, 32 | sendTrans: () => { 33 | const body = new Builder() 34 | .storeUint(0x18, 6) 35 | .storeAddress(randomAddress()) 36 | .storeCoins(toNano(0.1)) 37 | .storeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 38 | const msg = new Builder() 39 | .storeUint(OperationCodes.maintain, 32) 40 | .storeRef(body.endCell()) 41 | .storeUint(0, 8) 42 | .endCell() 43 | return msg 44 | }, 45 | sendAgain: () => { 46 | const msg = new Builder() 47 | .storeUint(OperationCodes.sendAgain, 32) 48 | .endCell() 49 | return msg 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/contracts/nft-raffle/raffle.source.ts: -------------------------------------------------------------------------------- 1 | import { Cell } from "ton"; 2 | import { combineFunc } from "../../utils/combineFunc"; 3 | 4 | 5 | export const RaffleSource = combineFunc(__dirname, [ 6 | "../sources/stdlib.fc", 7 | "../sources/nft-raffle/struct/constants.func", 8 | "../sources/nft-raffle/struct/storage.func", 9 | "../sources/nft-raffle/struct/utils.func", 10 | "../sources/nft-raffle/struct/handles.func", 11 | "../sources/nft-raffle/struct/get-methods.func", 12 | '../sources/nft-raffle/main.func', 13 | ]) 14 | 15 | export const NftRaffleCodeBOC = 'te6ccsECGgEABKgAAA0AEgAXAD0AQwCdANwA+AEgAV4BwQHoAh8CcQJ/Ar4CwwMRAy8DYwObA7cD8QQGBEUEqAEU/wD0pBP0vPLICwECAWIFAgIBWAQDAUW6KJ2zz4QvhD+ET4RfhG+Ef4SPhJ+Er4S/hM+E34TvhP+FCBkBBbtguBUEqNAz0NMDAXGw8kDbPPpAMALTH/hHUkDHBfhIUlDHBfhJUmDHBYIQBRONkVJQuo6GEDRfBNs84IEH0VJQuo6ObDMzgQPqWbFYsfL02zzgMIEH0lJAuhkNCgYDdI6YM4ED6lMxsfL0cAGSMHHeApJyMt5DMNs84FuBB9RSILqOhhAjXwPbPOAygQfTuo6C2zzgW4QP8vAJCAcANIED6fhCcbry8oED6vhJE8cFEvL01NMHMPsAAUqBA+n4QnO98vL4RfhDoPhOqIED61IiufLy+EX4Q6CpBPhQAds8EwNyMDGBA+n4QnG68vQhwAGd+EyCCJiWgFIgoaD4bN4BwAKc+E2CCJiWgBKhoPhtkTDi2zyOgts83ts8FQ8RBLqBA+n4QnG98vKBA+nbPPLycvhi+E/4TvhH+EjbPIIID0JAcPsC+Ef4TI0E05GVCByYWZmbGUgY2FuY2VsZWSBy2zz4SPhNjQTTkZUIHJhZmZsZSBjYW5jZWxlZIHIVDBILA0TbPPhJcI0E05GVCByYWZmbGUgY2FuY2VsZWSCBAILbPNs8EhIRAmZ/jy0kgwf0fG+lII8eItcLA8ACjoZxVHZS2zzeAtcLA8ADjoZxVHUx2zzekTLiAbPmXwUYGASYggiYloASofhOoSDBAPhCcb2xjoMw2zzgIvpEMfhPAds8XLGzjoRfBNs84DQ0+G8Cm/hGpPhm+EwioPhs3pv4RKT4ZPhNAaD4bZEw4hcWFw4DEts8joLbPN7bPBUPEQRyc/hi+FD4T9s8+HD4UPhO2zyCCA9CQHD7AvhJcI0FU5GVCByYWZmbGUgY29tbWlzc2lvboIEAgts8FBMSEAEE2zwRAJj4UPhPyPQA9ADJyPhK+gL4S/oC+Ez6AvhN+gL4TvoCycj4R88W+EjPFvhJzxbJ+Eb4RfhE+EP4QsjLAcsDywPLA8sDycjMzMzMye1UADhwIIAYyMsFUAbPFlAE+gIUy2oSyx8BzxbJAfsAAmB/jyoigwf0fG+lII8bAtcLAMABjohw+EdUZDHbPI6IcPhIVGQx2zzikTLiAbPmXwMYGABsf44xIYMH9HxvpTIhjiNy+BHAAJ1xyMsAydBUIAWDB/QWnXDIywDJ0FQgBYMH9BbiA94Bs+ZbATLbPPhD+ES6+EX4Rrqw+Ez4Sr6w+E34S76wGQBwcFRwEoMH9A5vocAAlF8EcCDg0wMwwACeMXLIywPJ0EADgwf0Fn+fMHPIywPJ0EADgwf0Fn8C4lgBJIBA1yH6QDAB+kQxgEACcALbPBgAenAg+CWCEF/MPRTIyx/LPyTPFlAEzxYTygAi+gISygDJcXAgcoAYyMsFywHLAMsHE8v/UAP6AstqzMkB+wAAwvhBbt3tRNDUAdDTAQH4YtMDAfhj0wMB+GTTAwH4ZdMDMPhm1AHQ+kAB+Gf6QAH4aPpAMPhp1AHQ+gAB+Gr6AAH4a/oAAfhs+gAB+G36ADD4btQw0PQEAfhv9AQw+HB/+GEoOcFW' 16 | export const NftRaffleCodeCell = Cell.fromBoc(Buffer.from(NftRaffleCodeBOC, 'base64'))[0] -------------------------------------------------------------------------------- /packages/contracts/nft-raffle/raffle.storage.ts: -------------------------------------------------------------------------------- 1 | import { Address, Builder, Cell, DictBuilder } from 'ton' 2 | import BN from 'bn.js' 3 | 4 | 5 | function buffer256ToDec (buff: Buffer): string { 6 | const build = new Builder().storeBuffer(buff).endCell() 7 | return build.beginParse().readUint(256).toString(10) 8 | } 9 | export interface RaffleStorage { 10 | state: number 11 | rightNftsCount: number 12 | leftNftsCount: number 13 | leftUser: Address 14 | rightUser: Address 15 | superUser: Address 16 | leftCommission: BN 17 | rightCommission: BN 18 | nftTransferFee: BN 19 | nfts: { address: Address, owner: 'left' | 'right'}[] 20 | } 21 | 22 | function encodeRaffleStorage 23 | ( 24 | raffleStorage: RaffleStorage 25 | ): Cell { 26 | const stateCell = new Builder() 27 | .storeUint(raffleStorage.state, 2) 28 | .storeUint(raffleStorage.rightNftsCount, 4) 29 | .storeUint(0, 4) 30 | .storeUint(raffleStorage.leftNftsCount, 4) 31 | .storeUint(0, 4) 32 | .endCell() 33 | const addrCell = new Builder() 34 | .storeAddress(raffleStorage.leftUser) 35 | .storeAddress(raffleStorage.rightUser) 36 | .storeAddress(raffleStorage.superUser) 37 | .endCell() 38 | const commissionCell = new Builder() 39 | .storeCoins(raffleStorage.leftCommission) 40 | .storeCoins(raffleStorage.rightCommission) 41 | .storeCoins(new BN(0)) 42 | .storeCoins(new BN(0)) 43 | .storeCoins(raffleStorage.nftTransferFee) 44 | .endCell() 45 | const nfts = new DictBuilder(256) 46 | if (raffleStorage.nfts.length > 0) { 47 | for (let i = 0; i < raffleStorage.nfts.length; i += 1) { 48 | const value = new Cell() 49 | const owner = raffleStorage.nfts[i].owner === 'left' ? 0 : 1 50 | value.bits.writeUint(owner, 4) 51 | nfts.storeCell(new BN(raffleStorage.nfts[i].address.hash), value) 52 | } 53 | } 54 | const dictCell = new Builder() 55 | .storeDict(nfts.endCell()) 56 | .storeDict(new DictBuilder(256).endDict()) 57 | const storageCell = new Builder() 58 | .storeRef(stateCell) 59 | .storeRef(addrCell) 60 | .storeRef(commissionCell) 61 | .storeRef(dictCell.endCell()) 62 | return storageCell.endCell() 63 | } 64 | 65 | export { encodeRaffleStorage, buffer256ToDec } 66 | -------------------------------------------------------------------------------- /packages/contracts/nft-swap/Swap.source.ts: -------------------------------------------------------------------------------- 1 | import {combineFunc} from "../../utils/combineFunc"; 2 | 3 | export const SwapSource = combineFunc(__dirname, [ 4 | '../sources/stdlib.fc', 5 | '../sources/op-codes.fc', 6 | '../sources/params.fc', 7 | '../sources/nft-swap.fc', 8 | ]) -------------------------------------------------------------------------------- /packages/contracts/nft-swap/SwapLocal.ts: -------------------------------------------------------------------------------- 1 | import {SmartContract} from "ton-contract-executor"; 2 | import { 3 | Address, 4 | Cell, 5 | contractAddress, parseDict, 6 | Slice, 7 | } from "ton"; 8 | import BN from "bn.js"; 9 | import { 10 | buildSwapDataCell, 11 | Queries, 12 | SwapData 13 | } from "./Swap.data"; 14 | import {SwapSource} from "./Swap.source"; 15 | import {compileFunc} from "../../utils/compileFunc"; 16 | 17 | type StateResponse = { state: number, left_ok: boolean, right_ok: boolean, 18 | leftAddr: Address, rightAddr: Address, leftNft: Map | null, rightNft: Map | null, 19 | leftComm: BN, leftAmount: BN, leftGot: BN, rightComm: BN, rightAmount: BN, rightGot: BN} 20 | 21 | export class SwapLocal { 22 | private constructor( 23 | public readonly contract: SmartContract, 24 | public readonly address: Address 25 | ) { 26 | 27 | } 28 | 29 | static queries = Queries 30 | 31 | // 32 | // Get methods 33 | // 34 | 35 | async getTradeState(): Promise { 36 | let res = await this.contract.invokeGetMethod('get_trade_state', []) 37 | if (res.type !== 'success') { 38 | throw new Error(`Cant invoke get_trade_state`) 39 | } 40 | 41 | let [state, left_ok, right_ok, leftAddr, rightAddr, leftNft, rightNft, 42 | leftComm, leftAmount, leftGot, rightComm, rightAmount, rightGot] = res.result as [BN, BN, BN, Slice, Slice, Cell, Cell, BN, BN, BN, BN, BN, BN] 43 | 44 | 45 | let leftMap = leftNft ? parseDict(leftNft.beginParse(),256, function (s: Slice) { 46 | return s.readBit(); 47 | }) : null 48 | let rightMap = rightNft ? parseDict(rightNft.beginParse(),256, function (s: Slice) { 49 | return s.readBit(); 50 | }) : null 51 | 52 | return { 53 | state: state ? state.toNumber() : 0, 54 | left_ok: !left_ok.isZero(), 55 | right_ok: !right_ok.isZero(), 56 | leftAddr: leftAddr.readAddress() as Address, 57 | rightAddr: rightAddr.readAddress() as Address, 58 | leftNft: leftMap, 59 | rightNft: rightMap, 60 | leftComm: leftComm, 61 | leftAmount: leftAmount, 62 | leftGot: leftGot, 63 | rightComm: rightComm, 64 | rightAmount: rightAmount, 65 | rightGot: rightGot, 66 | } 67 | } 68 | 69 | // 70 | // Internal messages 71 | // 72 | 73 | static async createFromConfig(config: SwapData) { 74 | let code = await compileFunc(SwapSource) 75 | 76 | let data = buildSwapDataCell(config) 77 | let contract = await SmartContract.fromCell(code.cell, data, { 78 | debug: true 79 | }) 80 | 81 | let address = contractAddress({ 82 | workchain: 0, 83 | initialData: contract.dataCell, 84 | initialCode: contract.codeCell 85 | }) 86 | 87 | contract.setC7Config({ 88 | myself: address 89 | }) 90 | 91 | return new SwapLocal(contract, address) 92 | } 93 | } -------------------------------------------------------------------------------- /packages/contracts/sbt-item/SbtItem.source.ts: -------------------------------------------------------------------------------- 1 | import {combineFunc} from "../../utils/combineFunc"; 2 | 3 | export const SbtItemSource = combineFunc(__dirname, [ 4 | '../sources/stdlib.fc', 5 | '../sources/op-codes.fc', 6 | '../sources/params.fc', 7 | '../sources/sbt-item.fc', 8 | ]) 9 | 10 | export const SbtSingleSource = combineFunc(__dirname, [ 11 | '../sources/stdlib.fc', 12 | '../sources/op-codes.fc', 13 | '../sources/params.fc', 14 | '../sources/sbt-single.fc', 15 | ]) 16 | -------------------------------------------------------------------------------- /packages/contracts/sources/deployer/deployer.base64: -------------------------------------------------------------------------------- 1 | te6cckEBBQEA8AABFP8A9KQT9LzyyAsBAaDTIMcAkl8E4AHQ0wMBcbCSXwTg+kAwAdMfghAFE42RUiC64wIzMyLAAZJfA+ACgQIruo4W7UTQ+kAwWMcF8uGT1DDQ0wfUMAH7AOBbhA/y8AIC/DHTP/pA0x+CCP4O3hK98tGU1NTRIfkAcMjKB8v/ydB3dIAYyMsFywIizxaCCTEtAPoCy2sTzMzJcfsAcCB0ghBfzD0UIoAYyMsFUAnPFiP6AhjLahfLHxXLPxXLAgHPFgHPFsoAIfoCygDJWaEggggPQkC5lIED6KDjDXD7AgMEAAwwgggPQkAACIMG+wAl44cc 2 | -------------------------------------------------------------------------------- /packages/contracts/sources/deployer/deployer.boc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hamaster2/nft-contracts/cee67b5bfca30d9b43d862b9e4e4a2de8b12036e/packages/contracts/sources/deployer/deployer.boc -------------------------------------------------------------------------------- /packages/contracts/sources/deployer/deployer.fc: -------------------------------------------------------------------------------- 1 | ;; Deployer for nft sales 2 | #include "../op-codes.fc"; 3 | 4 | int op::do_sale() asm "0x0fe0ede PUSHINT"; 5 | 6 | ;; storage scheme 7 | ;; storage#_ owner_address:MsgAddress 8 | ;; = Storage; 9 | 10 | 11 | () recv_internal(int balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 12 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 13 | return (); 14 | } 15 | slice cs = in_msg_full.begin_parse(); 16 | int flags = cs~load_uint(4); 17 | 18 | if (flags & 1) { ;; ignore all bounced messages 19 | return (); 20 | } 21 | slice sender_address = cs~load_msg_addr(); 22 | ;; cs.end_parse(); 23 | 24 | int op = in_msg_body~load_uint(32); 25 | 26 | if (op == op::ownership_assigned()) { ;; got nft for deploy 27 | int query_id = in_msg_body~load_uint(64); 28 | var prev_owner = in_msg_body~load_msg_addr(); 29 | int sub_op = in_msg_body~load_uint(32); 30 | throw_if(404, sub_op != op::do_sale()); 31 | (cell state_init, cell body) = (in_msg_body~load_ref(), in_msg_body~load_ref()); 32 | in_msg_body.end_parse(); 33 | int state_init_hash = cell_hash(state_init); 34 | slice dest_address = begin_cell().store_int(0, 8).store_uint(state_init_hash, 256).end_cell().begin_parse(); 35 | 36 | var msg = begin_cell() 37 | .store_uint(0x18, 6) 38 | .store_uint(4, 3).store_slice(dest_address) 39 | .store_grams(20000000) ;; 0.02 TON 40 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 41 | .store_ref(state_init) 42 | .store_ref(body) 43 | .end_cell(); 44 | send_raw_message(msg, 1); ;; paying fees, revert on errors 45 | 46 | var transfer_msg = begin_cell() 47 | .store_uint(0x18, 6) 48 | .store_slice(sender_address) 49 | .store_grams(0) 50 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 51 | .store_uint(op::transfer(), 32) 52 | .store_uint(query_id, 64) 53 | .store_uint(4, 3).store_slice(dest_address) ;; new owner 54 | .store_slice(prev_owner) ;; response_to 55 | .store_int(0, 1) ;; no payload 56 | .store_grams(0) ;; forward_amount 57 | .store_int(0, 1) ;; no body 58 | .end_cell(); 59 | var reserve = balance - msg_value; 60 | if (reserve < 1000000) { 61 | reserve = 1000000; ;; 0.001 TON 62 | } else { 63 | reserve += 1000; ;; 0.000001 TON for storage usage 64 | } 65 | raw_reserve(reserve, 0); ;; reserve some bebras 🐈 66 | send_raw_message(transfer_msg, 128); ;; paying fees, revert on errors 67 | 68 | return (); 69 | } 70 | ;; 71 | if (op == 1) { ;; accept for deploy 72 | return (); 73 | } 74 | 75 | if (op == 555) { 76 | var ds = get_data().begin_parse(); 77 | var owner_address = ds~load_msg_addr(); 78 | throw_unless(403, equal_slices(owner_address, sender_address)); 79 | ;; way to fix unexpected troubles with auction contract 80 | ;; for example if some one transfer nft to this contract 81 | var msg = in_msg_body~load_ref().begin_parse(); 82 | var mode = msg~load_uint(8); 83 | send_raw_message(msg~load_ref(), mode); 84 | return (); 85 | } 86 | 87 | throw(0xffff); 88 | } 89 | 90 | -------------------------------------------------------------------------------- /packages/contracts/sources/deployer/deployer.fif: -------------------------------------------------------------------------------- 1 | "TonUtil.fif" include 2 | "Asm.fif" include 3 | // automatically generated from `../stdlib.fc` `./deployer.fc` incl:`./../op-codes.fc` 4 | PROGRAM{ 5 | DECLPROC recv_internal 6 | recv_internal PROC:<{ 7 | DUP 8 | SEMPTY 9 | IFJMP:<{ 10 | 4 BLKDROP 11 | }> 12 | SWAP 13 | CTOS 14 | 4 LDU 15 | SWAP 16 | 1 PUSHINT 17 | AND 18 | IFJMP:<{ 19 | 4 BLKDROP 20 | }> 21 | LDMSGADDR 22 | DROP 23 | SWAP 24 | 32 LDU 25 | 0x05138d91 PUSHINT 26 | s2 s(-1) PUXC 27 | EQUAL 28 | IFJMP:<{ 29 | NIP 30 | 64 LDU 31 | LDMSGADDR 32 | 32 LDU 33 | 0x0fe0ede PUSHINT 34 | s1 s2 XCHG 35 | NEQ 36 | 404 THROWIF 37 | LDREF 38 | LDREF 39 | ENDS 40 | OVER 41 | HASHCU 42 | 0 PUSHINT 43 | NEWC 44 | 8 STI 45 | 256 STU 46 | ENDC 47 | CTOS 48 | 7 PUSHINT 49 | 4 PUSHINT 50 | 24 PUSHINT 51 | NEWC 52 | 6 STU 53 | 3 STU 54 | s2 PUSH 55 | STSLICER 56 | 20000000 PUSHINT 57 | STGRAMS 58 | 108 STU 59 | s1 s3 XCHG 60 | STREF 61 | STREF 62 | ENDC 63 | 1 PUSHINT 64 | SENDRAWMSG 65 | 0 PUSHINT 66 | DUP 67 | 4 PUSHINT 68 | 0x5fcc3d14 PUSHINT 69 | s2 PUSH 70 | 24 PUSHINT 71 | NEWC 72 | 6 STU 73 | s0 s9 XCHG2 74 | STSLICER 75 | s3 PUSH 76 | STGRAMS 77 | s1 s8 XCHG 78 | 107 STU 79 | s1 s7 XCHG 80 | 32 STU 81 | s1 s5 XCHG 82 | 64 STU 83 | s1 s5 XCHG 84 | 3 STU 85 | SWAP 86 | STSLICER 87 | SWAP 88 | STSLICER 89 | 1 STI 90 | OVER 91 | STGRAMS 92 | 1 STI 93 | ENDC 94 | -ROT 95 | SUB 96 | DUP 97 | 1000000 PUSHINT 98 | LESS 99 | IF:<{ 100 | DROP 101 | 1000000 PUSHINT 102 | }>ELSE<{ 103 | 1000 PUSHINT 104 | ADD 105 | }> 106 | 0 PUSHINT 107 | RAWRESERVE 108 | 7 PUSHPOW2 109 | SENDRAWMSG 110 | }> 111 | s3 POP 112 | s3 POP 113 | s2 PUSH 114 | 1 EQINT 115 | IFJMP:<{ 116 | 3 BLKDROP 117 | }> 118 | s0 s2 XCHG 119 | 555 PUSHINT 120 | EQUAL 121 | IFJMP:<{ 122 | c4 PUSH 123 | CTOS 124 | LDMSGADDR 125 | DROP 126 | ROT 127 | SDEQ 128 | 403 THROWIFNOT 129 | LDREF 130 | DROP 131 | CTOS 132 | 8 LDU 133 | LDREF 134 | DROP 135 | SWAP 136 | SENDRAWMSG 137 | }> 138 | 2DROP 139 | 16 PUSHPOW2DEC 140 | THROWANY 141 | }> 142 | }END>c 143 | -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v2/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | MINPUT="./nft-auction-v2.func" 4 | OUTPUT="./nft-auction-v2-code" 5 | FUNC_STDLIB_PATH="../stdlib.fc" 6 | 7 | echo "building \"${MINPUT}\"" 8 | 9 | # build you own func compiler 10 | /Users/i.nedzvetskiy/projects/ton/build/crypto/func -PA -o "${OUTPUT}.fif" ${FUNC_STDLIB_PATH} ${MINPUT} 11 | echo -e "\"TonUtil.fif\" include\n$(cat ${OUTPUT}.fif)" > "${OUTPUT}.fif" 12 | echo "\"${OUTPUT}.fif\" include 2 boc+>B \"${OUTPUT}.boc\" B>file" | fift -s 13 | base64 "${OUTPUT}.boc" > "${OUTPUT}.base64" -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v2/nft-auction-v2-code.base64: -------------------------------------------------------------------------------- 1 | te6cckECHQEABZMAART/APSkE/S88sgLAQIBIAIDAgFIBAUCKPIw2zyBA+74RMD/8vL4AH/4ZNs8GxwCAs4GBwKLoDhZtnm2eQQQgqqH8IXwofCH8KfwpfCd8JvwmfCX8JXwi/Cf8IwaIiYaGCIkGBYiIhYUIiAUIT4hHCD6INggtiD0INIgsRsaAgEgCAkCASAYGQT1AHQ0wMBcbDyQPpAMNs8+ELA//hDUiDHBbCO0DMx0x8hwACNBJyZXBlYXRfZW5kX2F1Y3Rpb26BSIMcFsI6DW9s84DLAAI0EWVtZXJnZW5jeV9tZXNzYWdlgUiDHBbCa1DDQ0wfUMAH7AOAw4PhTUhDHBY6EMzHbPOABgGxIKCwATIIQO5rKAAGphIAFcMYED6fhS10nCAvLygQPqAdMfghAFE42REroS8vSAQNch+kAw+HJw+GJ/+GTbPBwEhts8IMABjzgwgQPt+CP4UL7y8oED7fhCwP/y8oED8AKCEDuaygC5EvLy+FJSEMcF+ENSIMcFsfLhkwF/2zzbPOAgwAIMFQ0OAIwgxwDA/5IwcODTHzGLZjYW5jZWyCHHBZIwceCLRzdG9wghxwWSMHLgi2ZmluaXNoghxwWSMHLgi2ZGVwbG95gBxwWRc+BwAYpwIPglghBfzD0UyMsfyz/4Us8WUAPPFhLLACH6AssAyXGAGMjLBfhTzxZw+gLLasyCCA9CQHD7AsmDBvsAf/hif/hm2zwcBPyOwzAygQPt+ELA//LygQPwAYIQO5rKALny8vgj+FC+jhf4UlIQxwX4Q1IgxwWx+E1SIMcFsfLhk5n4UlIQxwXy4ZPi2zzgwAOSXwPg+ELA//gj+FC+sZdfA4ED7fLw4PhLghA7msoAoFIgvvhLwgCw4wL4UPhRofgjueMA+E4SDxARAiwCcNs8IfhtghA7msoAofhu+CP4b9s8FRIADvhQ+FGg+HADcI6VMoED6PhKUiC58vL4bvht+CP4b9s84fhO+EygUiC5l18DgQPo8vDgAnDbPAH4bfhu+CP4b9s8HBUcApT4TsAAjj1wIPglghBfzD0UyMsfyz/4Us8WUAPPFhLLACH6AssAyXGAGMjLBfhTzxZw+gLLasyCCA9CQHD7AsmDBvsA4w5/+GLbPBMcAvrbPPhOQFTwAyDCAI4rcCCAEMjLBVAHzxYi+gIWy2oVyx+L9NYXJrZXRwbGFjZSBmZWWM8WyXL7AJE04vhOQAPwAyDCAI4jcCCAEMjLBVAEzxYi+gITy2oSyx+LdSb3lhbHR5jPFsly+wCRMeKCCA9CQHD7AvhOWKEBoSDCABoUAMCOInAggBDIywX4Us8WUAP6AhLLassfi2UHJvZml0jPFsly+wCRMOJwIPglghBfzD0UyMsfyz/4Tc8WUAPPFhLLAIIImJaA+gLLAMlxgBjIywX4U88WcPoCy2rMyYMG+wAC8vhOwQGRW+D4TvhHoSKCCJiWgKFSELyZMAGCCJiWgKEBkTLijQpWW91ciBiaWQgaGFzIGJlZW4gb3V0YmlkIGJ5IGFub3RoZXIgdXNlci6ABwP+OHzCNBtBdWN0aW9uIGhhcyBiZWVuIGNhbmNlbGxlZC6DeIcIA4w8WFwA4cCCAGMjLBfhNzxZQBPoCE8tqEssfAc8WyXL7AAACWwARIIQO5rKAKmEgAB0IMAAk18DcOBZ8AIB8AGAAIPhI0PpA0x/TH/pA0x/THzAAyvhBbt3tRNDSAAH4YtIAAfhk0gAB+Gb6QAH4bfoAAfhu0x8B+G/THwH4cPpAAfhy1AH4aNQw+Gn4SdDSHwH4Z/pAAfhj+gAB+Gr6AAH4a/oAAfhs0x8B+HH6QAH4c9MfMPhlf/hhAFT4SfhI+FD4T/hG+ET4QsjKAMoAygD4Tc8W+E76Assfyx/4Us8WzMzJ7VQBqlR8 2 | -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v2/nft-auction-v2-code.boc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hamaster2/nft-contracts/cee67b5bfca30d9b43d862b9e4e4a2de8b12036e/packages/contracts/sources/nft-auction-v2/nft-auction-v2-code.boc -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v2/struct/exit-codes.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; custom TVM exit codes 3 | ;; 4 | 5 | int exit::low_bid() asm "1000 PUSHINT"; 6 | int exit::auction_init() asm "1001 PUSHINT"; 7 | int exit::no_transfer() asm "1002 PUSHINT"; 8 | int exit::not_message() asm "1003 PUSHINT"; 9 | int exit::not_cancel() asm "1004 PUSHINT"; 10 | int exit::auction_end() asm "1005 PUSHINT"; 11 | int exit::already_activated() asm "1006 PUSHINT"; 12 | int exit::cant_cancel_end() asm "1007 PUSHINT"; 13 | int exit::low_amount() asm "1008 PUSHINT"; 14 | 15 | -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v2/struct/get-met.func: -------------------------------------------------------------------------------- 1 | ;; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2 | (int, int, int, slice, slice, slice, int, slice, int, slice, int, int, slice, int, int, int, int, int, int, int) get_sale_data() method_id { 3 | init_data(); 4 | 5 | var ( 6 | mp_fee_addr, 7 | mp_fee_factor, 8 | mp_fee_base, 9 | royalty_fee_addr, 10 | royalty_fee_factor, 11 | royalty_fee_base 12 | ) = get_fees(); 13 | 14 | return ( 15 | 0x415543, ;; 1 nft aucion ("AUC") 16 | end?, ;; 2 17 | end_time, ;; 3 18 | mp_addr, ;; 4 19 | nft_addr, ;; 5 20 | nft_owner, ;; 6 21 | last_bid, ;; 7 22 | last_member, ;; 8 23 | min_step, ;; 9 24 | mp_fee_addr, ;; 10 25 | mp_fee_factor, mp_fee_base, ;; 11, 12 26 | royalty_fee_addr, ;; 13 27 | royalty_fee_factor, royalty_fee_base, ;; 14, 15 28 | max_bid, ;; 16 29 | min_bid, ;; 17 30 | created_at?, ;; 18 31 | last_bid_at, ;; 19 32 | is_canceled? ;; 20 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v2/struct/math.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; math utils 3 | ;; 4 | 5 | int division(int a, int b) { ;; division with factor 6 | return muldiv(a, 1000000000 {- 1e9 -}, b); 7 | } 8 | 9 | int multiply(int a, int b) { ;; multiply with factor 10 | return muldiv (a, b, 1000000000 {- 1e9 -}); 11 | } 12 | 13 | int math::get_percent(int a, int percent, int factor) { 14 | if (factor == 0) { 15 | return 0; 16 | } else { 17 | return division(multiply(a, percent), factor); 18 | } 19 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v2/struct/msg-utils.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; text constants for msg comments 3 | ;; 4 | 5 | slice msg::cancel_msg() asm " 2); ;; throw if auction already init 10 | throw_unless(exit::no_transfer(), in_msg_body~load_uint(32) == op::ownership_assigned()); ;; throw if it`s not ownership assigned 11 | in_msg_body~skip_bits(64); ;; query id 12 | nft_owner = in_msg_body~load_msg_addr(); 13 | end? = false; 14 | activated? = true; 15 | pack_data(); 16 | } 17 | 18 | 19 | () handle::cancel(slice sender_addr) impure inline_ref { 20 | builder nft_transfer_body = begin_cell() 21 | .store_uint(op::transfer(), 32) 22 | .store_uint(cur_lt(), 64) ;; query id 23 | .store_slice(nft_owner) ;; return nft no creator 24 | .store_slice(sender_addr) ;; response_destination 25 | .store_uint(0, 1) ;; custom payload 26 | .store_coins(0) ;; forward amount 27 | .store_uint(0, 1); ;; forward payload 28 | 29 | builder nft_return_msg = begin_cell() 30 | .store_uint(0x18, 6) 31 | .store_slice(nft_addr) 32 | .store_coins(0) 33 | .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) 34 | .store_ref(nft_transfer_body.end_cell()); 35 | 36 | raw_reserve(1000000, 0); ;; reserve some bebras 🐈 37 | 38 | send_raw_message(nft_return_msg.end_cell(), 128); 39 | end? = true; 40 | is_canceled? = true; 41 | pack_data(); 42 | } 43 | 44 | () handle::end_auction(slice sender_addr) impure inline_ref { 45 | if (last_bid == 0) { ;; just return nft 46 | handle::cancel(sender_addr); 47 | return (); 48 | } 49 | 50 | var ( 51 | mp_fee_addr, 52 | mp_fee_factor, 53 | mp_fee_base, 54 | royalty_fee_addr, 55 | royalty_fee_factor, 56 | royalty_fee_base 57 | ) = get_fees(); 58 | 59 | int mp_fee = math::get_percent(last_bid, mp_fee_factor, mp_fee_base); 60 | 61 | if (mp_fee > 0) { 62 | builder mp_transfer = begin_cell() 63 | .store_uint(0x10, 6) ;; 0 (int_msg_info) 1 (ihr_disabled) 1 (no bounces) 00 (address) 64 | .store_slice(mp_fee_addr) 65 | .store_coins(mp_fee) 66 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 67 | .store_uint(0, 32) 68 | .store_slice(msg::mp_msg()); 69 | 70 | send_raw_message(mp_transfer.end_cell(), 2); 71 | } 72 | 73 | int royalty_fee = math::get_percent(last_bid, royalty_fee_factor, royalty_fee_base); 74 | 75 | if (royalty_fee > 0) { 76 | builder royalty_transfer = begin_cell() 77 | .store_uint(0x10, 6) ;; 0 (int_msg_info) 1 (ihr_disabled) 1 (no bounces) 00 (address) 78 | .store_slice(royalty_fee_addr) 79 | .store_coins(royalty_fee) 80 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 81 | .store_uint(0, 32) 82 | .store_slice(msg::royalty_msg()); 83 | 84 | send_raw_message(royalty_transfer.end_cell(), 2); 85 | } 86 | 87 | raw_reserve(1000000, 0); ;; reserve some bebras 🐈 88 | 89 | int profit = last_bid - mp_fee - royalty_fee; 90 | if (profit > 0) { 91 | builder prev_owner_msg = begin_cell() 92 | .store_uint(0x10, 6) ;; 0 (int_msg_info) 1 (ihr_disabled) 1 (no bounces) 00 (address) 93 | .store_slice(nft_owner) 94 | .store_coins(profit) 95 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 96 | .store_uint(0, 32) 97 | .store_slice(msg::profit_msg()); 98 | 99 | send_raw_message(prev_owner_msg.end_cell(), 2); 100 | } 101 | 102 | builder nft_transfer_body = begin_cell() 103 | .store_uint(op::transfer(), 32) 104 | .store_uint(cur_lt(), 64) ;; query id 105 | .store_slice(last_member) ;; new owner 106 | .store_slice(sender_addr) ;; response_destination 107 | .store_uint(0, 1) ;; custom payload 108 | .store_coins(10000000) ;; forward amount 0.01 ton 109 | .store_uint(0, 1); ;; forward payload 110 | builder nft_transfer = begin_cell() 111 | .store_uint(0x18, 6) 112 | .store_slice(nft_addr) 113 | .store_coins(0) 114 | .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) 115 | .store_ref(nft_transfer_body.end_cell()); 116 | send_raw_message(nft_transfer.end_cell(), 128); 117 | end? = true; 118 | pack_data(); 119 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v3r2/struct/math.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; math utils 3 | ;; 4 | 5 | int division(int a, int b) { ;; division with factor 6 | return muldiv(a, 1000000000 {- 1e9 -}, b); 7 | } 8 | 9 | int multiply(int a, int b) { ;; multiply with factor 10 | return muldiv (a, b, 1000000000 {- 1e9 -}); 11 | } 12 | 13 | int math::get_percent(int a, int percent, int factor) { 14 | if (factor == 0) { 15 | return 0; 16 | } else { 17 | return division(multiply(a, percent), factor); 18 | } 19 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction-v3r2/struct/msg-utils.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; text constants for msg comments 3 | ;; 4 | 5 | slice msg::cancel_msg() asm " "${OUTPUT}.fif" 12 | echo "\"${OUTPUT}.fif\" include 2 boc+>B \"${OUTPUT}.boc\" B>file" | fift -s 13 | base64 "${OUTPUT}.boc" > "${OUTPUT}.base64" -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction/nft-auction-code.base64: -------------------------------------------------------------------------------- 1 | te6cckECLgEABqIAART/APSkE/S88sgLAQIBIAIDAgFIBAUCKPIw2zyBA+74RMD/8vL4AH/4ZNs8LBkCAs4GBwIBIBwdAgEgCAkCASAaGwT1DPQ0wMBcbDyQPpAMNs8+ENSEMcF+EKwwP+Oz1vTHyHAAI0EnJlcGVhdF9lbmRfYXVjdGlvboFIgxwWwjoNb2zzgAcAAjQRZW1lcmdlbmN5X21lc3NhZ2WBSIMcFsJrUMNDTB9QwAfsA4DDg+FdSEMcFjoQxAds84PgjgLBEKCwATIIQO5rKAAGphIAFcMYED6fhW10nCAvLygQPqAdMfghAFE42REroS8vSAQNch+kAw+HZw+GJ/+GTbPBkESPhTvo8GbCHbPNs84PhCwP+OhGwh2zzg+FZSEMcF+ENSIMcFsRcRFwwEeI+4MYED6wLTHwHDABPy8otmNhbmNlbIUiDHBY6DIds83otHN0b3CBLHBfhWUiDHBbCPBNs82zyRMOLgMg0XEQ4B9oED7ItmNhbmNlbIEscFs/Ly+FHCAI5FcCCAGMjLBfhQzxb4UfoCy2rLH40KVlvdXIgYmlkIGhhcyBiZWVuIG91dGJpZCBieSBhbm90aGVyIHVzZXIugzxbJcvsA3nAg+CWCEF/MPRTIyx/LP/hWzxb4Vs8WywAh+gLLAA8BBNs8EAFMyXGAGMjLBfhXzxZw+gLLasyCCA9CQHD7AsmDBvsAf/hif/hm2zwZBPSBA+34QsD/8vL4U/gjuY8FMNs82zzg+E7CAPhOUiC+sI7V+FGORXAggBjIywX4UM8W+FH6Astqyx+NClZb3VyIGJpZCBoYXMgYmVlbiBvdXRiaWQgYnkgYW5vdGhlciB1c2VyLoM8WyXL7AN4B+HD4cfgj+HLbPOD4UxcRERICkvhRwACOPHAg+CWCEF/MPRTIyx/LP/hWzxb4Vs8WywAh+gLLAMlxgBjIywX4V88WcPoCy2rMgggPQkBw+wLJgwb7AOMOf/hi2zwTGQP8+FWh+CO5l/hT+FSg+HPe+FGOlIED6PhNUiC58vL4cfhw+CP4cts84fhR+E+gUhC5joMw2zzgcCCAGMjLBfhQzxb4UfoCy2rLH40KVlvdXIgYmlkIGhhcyBiZWVuIG91dGJpZCBieSBhbm90aGVyIHVzZXIugzxbJcvsAAfhwGRcYA/hwIPglghBfzD0UyMsfyz/4UM8W+FbPFssAggnJw4D6AssAyXGAGMjLBfhXzxaCEDuaygD6AstqzMly+wD4UfhI+EnwAyDCAJEw4w34UfhL+EzwAyDCAJEw4w2CCA9CQHD7AnAggBjIywX4Vs8WIfoCy2rLH4nPFsmDBvsAFBUWAHhwIIAYyMsF+EfPFlAD+gISy2rLH40H01hcmtldHBsYWNlIGNvbW1pc3Npb24gd2l0aGRyYXeDPFslz+wAAcHAggBjIywX4Ss8WUAP6AhLLassfjQbUm95YWx0eSBjb21taXNzaW9uIHdpdGhkcmF3gzxbJc/sAAC5QcmV2aW91cyBvd25lciB3aXRoZHJhdwCIcCCAGMjLBVADzxYh+gISy2rLH40J1lvdXIgdHJhbnNhY3Rpb24gaGFzIG5vdCBiZWVuIGFjY2VwdGVkLoM8WyYBA+wABEPhx+CP4cts8GQDQ+Ez4S/hJ+EjI+EfPFssfyx/4Ss8Wyx/LH/hV+FT4U/hSyPhN+gL4TvoC+E/6AvhQzxb4UfoCyx/LH8sfyx/I+FbPFvhXzxbJAckCyfhG+EX4RPhCyMoA+EPPFsoAyh/KAMwSzMzJ7VQAESCEDuaygCphIAANFnwAgHwAYAIBIB4fAgEgJCUCAWYgIQElupFds8+FbXScEDknAg4PhW+kSCwBEa8u7Z58KH0iQCwCASAiIwEYqrLbPPhI+En4S/hMLAFeqCzbPIIIQVVD+EL4U/hD+Ff4VvhR+FD4T/hH+Ej4SfhK+Ev4TPhO+E34RfhS+EYsAgEgJicCAW4qKwEdt++7Z58JvwnfCf8KPwpwLAIBICgpARGwybbPPhK+kSAsARGxlvbPPhH+kSAsARGvK22efCH9IkAsASWsre2efCvrpOCByTgQcHwr/SJALAH2+EFu3e1E0NIAAfhi+kAB+GPSAAH4ZNIfAfhl0gAB+GbUAdD6QAH4Z9MfAfho0x8B+Gn6QAH4atMfAfhr0x8w+GzUAdD6AAH4bfoAAfhu+gAB+G/6QAH4cPoAAfhx0x8B+HLTHwH4c9MfAfh00x8w+HXUMND6QAH4dvpALQAMMPh3f/hhRQVNYw== 2 | -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction/nft-auction-code.boc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hamaster2/nft-contracts/cee67b5bfca30d9b43d862b9e4e4a2de8b12036e/packages/contracts/sources/nft-auction/nft-auction-code.boc -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction/nft-auction.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; main FunC file git@github.com:cryshado/nft-auc-contest.git 3 | ;; 4 | 5 | int equal_slices? (slice a, slice b) asm "SDEQ"; 6 | 7 | #include "struct/op-codes.func"; 8 | #include "struct/exit-codes.func"; 9 | #include "struct/math.func"; 10 | #include "struct/msg-utils.func"; 11 | #include "struct/storage.func"; 12 | #include "struct/handles.func"; 13 | #include "struct/get-met.func"; 14 | 15 | {- 16 | SHOULD 17 | [+] accept coins for deploy 18 | [+] accept nft and change auction statud 19 | [+] return transaction if auction already end 20 | [+] can cancel auction 21 | [+] accept new bid -> check auction end -> end auction 22 | -} 23 | () recv_internal(int my_balance, int msg_value, cell in_msg_cell, slice in_msg_body) impure { 24 | slice cs = in_msg_cell.begin_parse(); 25 | throw_if(0, cs~load_uint(4) & 1); 26 | 27 | slice sender_addr = cs~load_msg_addr(); 28 | init_data(); 29 | 30 | if (equal_slices?(sender_addr, mp_addr) & end? == true) { 31 | int op = in_msg_body~load_uint(32); 32 | if ((op == 0) & equal_slices(in_msg_body, msg::repeat_end_auction())) { 33 | ;; special case for repeat end_auction logic if nft not transfered from auc contract 34 | handle::end_auction(); 35 | return (); 36 | } 37 | if ((op == 0) & equal_slices(in_msg_body, msg::emergency_message())) { 38 | ;; way to fix unexpected troubles with auction contract 39 | ;; for example if some one transfer nft to this contract 40 | var msg = in_msg_body~load_ref().begin_parse(); 41 | var mode = msg~load_uint(8); 42 | send_raw_message(msg~load_ref(), mode); 43 | return (); 44 | } 45 | ;; accept coins for deploy 46 | return (); 47 | } 48 | 49 | if (equal_slices?(sender_addr, nft_addr)) { 50 | handle::try_init_auction(sender_addr, in_msg_body); 51 | return (); 52 | } 53 | 54 | if (now() >= end_time) { 55 | handle::return_transaction(sender_addr); 56 | handle::end_auction(); 57 | return (); 58 | } 59 | 60 | if (end? == true) { 61 | handle::return_transaction(sender_addr); 62 | return (); 63 | } 64 | 65 | if (equal_slices?(sender_addr, nft_owner)) | (equal_slices?(sender_addr, mp_addr)) { 66 | throw_if(;;throw if it`s not message 67 | exit::not_message(), 68 | in_msg_body~load_uint(32) != 0 69 | ); 70 | 71 | if (equal_slices?(in_msg_body, msg::cancel_msg())) { 72 | handle::try_cancel(in_msg_body); 73 | } 74 | 75 | if (equal_slices?(in_msg_body, msg::stop_msg()) & equal_slices?(sender_addr, nft_owner)) { 76 | handle::return_transaction(sender_addr); 77 | handle::end_auction(); 78 | } 79 | 80 | return (); 81 | } 82 | 83 | handle::new_bid(sender_addr, msg_value); 84 | } 85 | 86 | {- 87 | Message for deploy contract external 88 | -} 89 | () recv_external(slice in_msg) impure { 90 | init_data(); 91 | throw_if(exit::already_activated(), activated? == true); 92 | accept_message(); 93 | activated? = true; 94 | pack_data(); 95 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction/struct/exit-codes.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; custom TVM exit codes 3 | ;; 4 | 5 | int exit::low_bid() asm "1000 PUSHINT"; 6 | int exit::auction_init() asm "1001 PUSHINT"; 7 | int exit::no_transfer() asm "1002 PUSHINT"; 8 | int exit::not_message() asm "1003 PUSHINT"; 9 | int exit::not_cancel() asm "1004 PUSHINT"; 10 | int exit::auction_end() asm "1005 PUSHINT"; 11 | int exit::already_activated() asm "1006 PUSHINT"; 12 | -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction/struct/get-met.func: -------------------------------------------------------------------------------- 1 | (int, int) get_nft_owner() method_id { 2 | init_data(); 3 | 4 | if (nft_owner.slice_bits() <= 2) { return (0, 0);} 5 | 6 | (int wc, int addr) = parse_std_addr(nft_owner); 7 | return (wc, addr); 8 | } 9 | 10 | (int, int) get_nft_addr() method_id { 11 | init_data(); 12 | 13 | if (nft_addr.slice_bits() <= 2) { return (0, 0);} 14 | 15 | (int wc, int addr) = parse_std_addr(nft_addr); 16 | return (wc, addr); 17 | } 18 | 19 | (int, int) get_last_member() method_id { 20 | init_data(); 21 | ;; trhow error of addr not std 22 | (int wc, int addr) = parse_std_addr(last_member); 23 | return (wc, addr); 24 | } 25 | 26 | (int, int) get_mp_addr() method_id { 27 | init_data(); 28 | ;; trhow error of addr not std 29 | (int wc, int addr) = parse_std_addr(mp_addr); 30 | return (wc, addr); 31 | } 32 | 33 | (int, int) get_mp_fee_addr() method_id { 34 | init_data(); 35 | ;; trhow error of addr not std 36 | (int wc, int addr) = parse_std_addr(mp_fee_addr); 37 | return (wc, addr); 38 | } 39 | 40 | (int, int) get_royalty_fee_addr() method_id { 41 | init_data(); 42 | ;; trhow error of addr not std 43 | (int wc, int addr) = parse_std_addr(royalty_fee_addr); 44 | return (wc, addr); 45 | } 46 | 47 | (int, int, int, int) get_fees_info() method_id { 48 | init_data(); 49 | return ( 50 | mp_fee_factor, mp_fee_base, 51 | royalty_fee_factor, royalty_fee_base 52 | ); 53 | } 54 | 55 | (int, int, int, int, int) get_bid_info() method_id { 56 | init_data(); 57 | return ( 58 | min_bid, max_bid, min_step, 59 | last_bid, end_time 60 | ); 61 | } 62 | 63 | ;; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 64 | (int, int, int, slice, slice, slice, int, slice, int, slice, int, int, slice, int, int, int, int, int, int, int) get_sale_data() method_id { 65 | init_data(); 66 | 67 | return ( 68 | 0x415543, ;; 1 nft aucion ("AUC") 69 | end?, ;; 2 70 | end_time, ;; 3 71 | mp_addr, ;; 4 72 | nft_addr, ;; 5 73 | nft_owner, ;; 6 74 | last_bid, ;; 7 75 | last_member, ;; 8 76 | min_step, ;; 9 77 | mp_fee_addr, ;; 10 78 | mp_fee_factor, mp_fee_base, ;; 11, 12 79 | royalty_fee_addr, ;; 13 80 | royalty_fee_factor, royalty_fee_base, ;; 14, 15 81 | max_bid, ;; 16 82 | min_bid, ;; 17 83 | created_at?, ;; 18 84 | last_bid_at, ;; 19 85 | is_canceled? ;; 20 86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction/struct/math.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; math utils 3 | ;; 4 | 5 | int division(int a, int b) { ;; division with factor 6 | return muldiv(a, 1000000000 {- 1e9 -}, b); 7 | } 8 | 9 | int multiply(int a, int b) { ;; multiply with factor 10 | return muldiv (a, b, 1000000000 {- 1e9 -}); 11 | } 12 | 13 | int math::get_percent(int a, int percent, int factor) { 14 | return division(multiply(a, percent), factor); 15 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-auction/struct/msg-utils.func: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; text constants for msg comments 3 | ;; 4 | 5 | slice msg::cancel_msg() asm " 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 packages: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 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-marketplace-v2.fc: -------------------------------------------------------------------------------- 1 | ;; NFT marketplace smart contract v2 2 | ;; Extends wallet v3r2 & adds ability to deploy sales 3 | 4 | ;; 5 | ;; storage scheme 6 | ;; 7 | ;; storage#_ seqno:uint32 subwallet:uint32 public_key:uint25 8 | ;; = Storage; 9 | ;; 10 | _ load_data() { 11 | var ds = get_data().begin_parse(); 12 | return ( 13 | ds~load_uint(32), ;; seqno 14 | ds~load_uint(32), ;; subwallet 15 | ds~load_uint(256) ;; public_key 16 | ); 17 | } 18 | 19 | () store_data(var data) impure { 20 | ( 21 | int seqno, 22 | int subwallet, 23 | int public_key 24 | ) = data; 25 | 26 | set_data( 27 | begin_cell() 28 | .store_uint(seqno, 32) 29 | .store_uint(subwallet, 32) 30 | .store_uint(public_key, 256) 31 | .end_cell() 32 | ); 33 | } 34 | 35 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 36 | if (in_msg_body.slice_empty?()) { ;; ignore empty messages 37 | return (); 38 | } 39 | 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 | var (seqno, subwallet, public_key) = load_data(); 48 | 49 | int op = in_msg_body~load_uint(32); 50 | 51 | if (op == 1) { ;; deploy new signed sale 52 | var signature = in_msg_body~load_bits(512); 53 | throw_unless(35, check_signature(slice_hash(in_msg_body), signature, public_key)); 54 | 55 | (cell state_init, cell body) = (in_msg_body~load_ref(), in_msg_body~load_ref()); 56 | 57 | int state_init_hash = cell_hash(state_init); 58 | slice dest_address = begin_cell().store_int(0, 8).store_uint(state_init_hash, 256).end_cell().begin_parse(); 59 | 60 | var msg = begin_cell() 61 | .store_uint(0x18, 6) 62 | .store_uint(4, 3).store_slice(dest_address) 63 | .store_grams(0) 64 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 65 | .store_ref(state_init) 66 | .store_ref(body); 67 | 68 | send_raw_message(msg.end_cell(), 64); ;; carry remaining value of message 69 | return (); 70 | } 71 | 72 | return (); 73 | } 74 | 75 | () recv_external(slice in_msg) impure { 76 | var signature = in_msg~load_bits(512); 77 | var cs = in_msg; 78 | var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); 79 | throw_if(35, valid_until <= now()); 80 | var (seqno, subwallet, public_key) = load_data(); 81 | throw_unless(33, msg_seqno == seqno); 82 | throw_unless(34, subwallet_id == subwallet); 83 | throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); 84 | accept_message(); 85 | cs~touch(); 86 | while (cs.slice_refs()) { 87 | var mode = cs~load_uint(8); 88 | send_raw_message(cs~load_ref(), mode); 89 | } 90 | 91 | store_data( 92 | seqno + 1, 93 | subwallet, 94 | public_key 95 | ); 96 | } 97 | 98 | ;; Get methods 99 | 100 | int seqno() method_id { 101 | return get_data().begin_parse().preload_uint(32); 102 | } 103 | 104 | int get_public_key() method_id { 105 | var cs = get_data().begin_parse(); 106 | cs~load_uint(64); 107 | return cs.preload_uint(256); 108 | } -------------------------------------------------------------------------------- /packages/contracts/sources/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 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-raffle/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MINPUT="./main.func" 4 | OUTPUT="./code" 5 | 6 | echo "building \"${MINPUT}\"" 7 | 8 | function errvar { 9 | echo "[ERR] \"FUNC_STDLIB_PATH\" and \"FIFTPATH\" env vars must be set" 10 | exit 11 | } 12 | 13 | [[ -z "${FIFTPATH}" ]] && errvar || : 14 | 15 | func -PA -o "${OUTPUT}.fif" ${MINPUT} 16 | echo -e "\"TonUtil.fif\" include\n$(cat ${OUTPUT}.fif)" > "${OUTPUT}.fif" 17 | echo "\"${OUTPUT}.fif\" include 2 boc+>B \"${OUTPUT}.boc\" B>file" | fift -s -------------------------------------------------------------------------------- /packages/contracts/sources/nft-raffle/main.func: -------------------------------------------------------------------------------- 1 | 2 | 3 | {- 4 | [+] Receive NFT`s 5 | [+] Raffle NFT`s and send NFT`s to the new owners 6 | [+] Cancel and send NFT`s to the owners 7 | [+] Receive extra TON`s for comission 8 | [+] If we have errors we should can send NFT`s 9 | -} 10 | 11 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { 12 | slice cs = in_msg_full.begin_parse(); 13 | throw_if(0, cs~load_uint(4) & 1); 14 | init_data(); 15 | 16 | slice sender_addr = cs~load_msg_addr(); 17 | int op = in_msg_body~load_uint(32); 18 | 19 | int left? = equal_slices(sender_addr, db::left_user); 20 | int right? = equal_slices(sender_addr, db::right_user); 21 | int super? = equal_slices(sender_addr, db::super_user); 22 | 23 | if (op == op::ownership_assigned()) { 24 | handle::receive_nft(sender_addr, msg_value, in_msg_body); 25 | return (); 26 | } 27 | if (op == op::cancel()) { 28 | throw_unless(err::wrong_addr(), left? | right? | super?); 29 | handle::cancel(); 30 | return (); 31 | } 32 | 33 | if (op == op::add_coins()) { 34 | throw_unless(err::wrong_addr(), right? | left?); 35 | int sender = 0; 36 | if (left?) { 37 | sender = 1; 38 | } 39 | if (right?) { 40 | sender = 2; 41 | } 42 | handle::add_coins(sender, sender_addr, msg_value, in_msg_body); 43 | return (); 44 | } 45 | 46 | if (op == op::send_again()) { 47 | handle::send_again(msg_value); 48 | return (); 49 | } 50 | 51 | if (op == op::maintain()) { 52 | handle::maintain(sender_addr, in_msg_body); 53 | return(); 54 | } 55 | throw(0xffff); 56 | } -------------------------------------------------------------------------------- /packages/contracts/sources/nft-raffle/struct/constants.func: -------------------------------------------------------------------------------- 1 | int state::active() asm "1 PUSHINT"; 2 | int state::canceled() asm "2 PUSHINT"; 3 | int state::completed() asm "3 PUSHINT"; 4 | 5 | int min_tons_for_storage() asm "1000000 PUSHINT"; ;; 0,001 TON 6 | int min_tons_for_operation() asm "10000000 PUSHINT"; ;; 0,01 TON 7 | 8 | int op::transfer() asm "0x5fcc3d14 PUSHINT"; 9 | int op::ownership_assigned() asm "0x05138d91 PUSHINT"; 10 | int op::cancel() asm "2001 PUSHINT"; 11 | int op::add_coins() asm "2002 PUSHINT"; 12 | int op::maintain() asm "2003 PUSHINT"; 13 | int op::send_again() asm "2004 PUSHINT"; 14 | 15 | slice msg::canceled() asm " = db::left_commission) & (db::right_coins_got >= db::right_commission)); 88 | } 89 | 90 | () utils::send_tons(slice to, int amount, slice message, int mode) impure inline_ref { 91 | var msg = begin_cell() 92 | .store_uint(0x18, 6) 93 | .store_slice(to) 94 | .store_coins(amount) 95 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 96 | .store_uint(0, 32) 97 | .store_slice(message); 98 | send_raw_message(msg.end_cell(), mode); 99 | } -------------------------------------------------------------------------------- /packages/contracts/sources/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 | int op::fix_price_v4_deploy_jetton() asm "0xfb5dbf47 PUSHINT"; 9 | int op::fix_price_v4_deploy_blank() asm "0x664c0905 PUSHINT"; 10 | int op::fix_price_v4_cancel() asm "0x3 PUSHINT"; 11 | int op::fix_price_v4_change_price() asm "0xfd135f7b PUSHINT"; 12 | int op::fix_price_v4_buy() asm "0x2 PUSHINT"; 13 | 14 | ;; NFTEditable 15 | int op::edit_content() asm "0x1a0b9d51 PUSHINT"; 16 | int op::transfer_editorship() asm "0x1c04412a PUSHINT"; 17 | int op::editorship_assigned() asm "0x511a4463 PUSHINT"; 18 | 19 | ;; SBT 20 | int op::request_owner() asm "0xd0c3bfea PUSHINT"; 21 | int op::owner_info() asm "0x0dd607e3 PUSHINT"; 22 | 23 | int op::prove_ownership() asm "0x04ded148 PUSHINT"; 24 | int op::ownership_proof() asm "0x0524c7ae PUSHINT"; 25 | int op::ownership_proof_bounced() asm "0xc18e86d2 PUSHINT"; 26 | 27 | int op::destroy() asm "0x1f04537a PUSHINT"; 28 | int op::revoke() asm "0x6f89f5e3 PUSHINT"; 29 | int op::take_excess() asm "0xd136d3b3 PUSHINT"; 30 | 31 | int jetton::transfer_notification() asm "0x7362d09c PUSHINT"; 32 | int jetton::transfer() asm "0xf8a7ea5 PUSHINT"; 33 | -------------------------------------------------------------------------------- /packages/contracts/sources/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 | } 7 | 8 | slice null_addr() asm "b{00} PUSHSLICE"; 9 | int flag::regular() asm "0x10 PUSHINT"; 10 | int flag::bounce() asm "0x8 PUSHINT"; -------------------------------------------------------------------------------- /packages/nft-content/nftContent.spec.ts: -------------------------------------------------------------------------------- 1 | import {decodeOffChainContent, encodeOffChainContent, flattenSnakeCell, makeSnakeCell} from "./nftContent"; 2 | 3 | describe('nft content encoder', () => { 4 | it('should encode off chain content', async () => { 5 | let text = `Apple was founded as Apple Computer Company on April 1, 1976, by Steve Jobs, Steve Wozniak and Ronald Wayne to develop and sell Wozniak's Apple I personal computer. It was incorporated by Jobs and Wozniak as Apple Computer, Inc. in 1977 and the company's next computer, the Apple II became a best seller. Apple went public in 1980, to instant financial success. The company went onto develop new computers featuring innovative graphical user interfaces, including the original Macintosh, announced in a critically acclaimed advertisement, "1984", directed by Ridley Scott. By 1985, the high cost of its products and power struggles between executives caused problems. Wozniak stepped back from Apple amicably, while Jobs resigned to found NeXT, taking some Apple employees with him.` 6 | 7 | expect(decodeOffChainContent(encodeOffChainContent(text))).toEqual(text) 8 | }) 9 | }) -------------------------------------------------------------------------------- /packages/nft-content/nftContent.ts: -------------------------------------------------------------------------------- 1 | import {Cell} from "ton"; 2 | 3 | const OFF_CHAIN_CONTENT_PREFIX = 0x01 4 | 5 | export function flattenSnakeCell(cell: Cell) { 6 | let c: Cell|null = cell 7 | 8 | let res = Buffer.alloc(0) 9 | 10 | while (c) { 11 | let cs = c.beginParse() 12 | let data = cs.readRemainingBytes() 13 | res = Buffer.concat([res, data]) 14 | c = c.refs[0] 15 | } 16 | 17 | return res 18 | } 19 | 20 | function bufferToChunks(buff: Buffer, chunkSize: number) { 21 | let chunks: Buffer[] = [] 22 | while (buff.byteLength > 0) { 23 | chunks.push(buff.slice(0, chunkSize)) 24 | buff = buff.slice(chunkSize) 25 | } 26 | return chunks 27 | } 28 | 29 | export function makeSnakeCell(data: Buffer) { 30 | let chunks = bufferToChunks(data, 127) 31 | let rootCell = new Cell() 32 | let curCell = rootCell 33 | 34 | for (let i = 0; i < chunks.length; i++) { 35 | let chunk = chunks[i] 36 | 37 | curCell.bits.writeBuffer(chunk) 38 | 39 | if (chunks[i+1]) { 40 | let nextCell = new Cell() 41 | curCell.refs.push(nextCell) 42 | curCell = nextCell 43 | } 44 | } 45 | 46 | return rootCell 47 | } 48 | 49 | export function encodeOffChainContent(content: string) { 50 | let data = Buffer.from(content) 51 | let offChainPrefix = Buffer.from([OFF_CHAIN_CONTENT_PREFIX]) 52 | data = Buffer.concat([offChainPrefix, data]) 53 | return makeSnakeCell(data) 54 | } 55 | 56 | export function decodeOffChainContent(content: Cell) { 57 | let data = flattenSnakeCell(content) 58 | 59 | let prefix = data[0] 60 | if (prefix !== OFF_CHAIN_CONTENT_PREFIX) { 61 | throw new Error(`Unknown content prefix: ${prefix.toString(16)}`) 62 | } 63 | return data.slice(1).toString() 64 | } -------------------------------------------------------------------------------- /packages/utils/combineFunc.ts: -------------------------------------------------------------------------------- 1 | import {readFileSync} from "fs"; 2 | import {resolve} from "path"; 3 | 4 | export function combineFunc(root: string, paths: string[]) { 5 | let res = '' 6 | 7 | for (let path of paths) { 8 | res += readFileSync(resolve(root, path), 'utf-8') 9 | res += '\n' 10 | } 11 | 12 | return res 13 | } -------------------------------------------------------------------------------- /packages/utils/compileFunc.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Unlike ton-compiler's compileFunc this function don't include stdlib.fc 3 | // 4 | import {readFile, writeFile} from "fs/promises"; 5 | import {compileFift, executeFunc} from "ton-compiler"; 6 | import {createTempFile} from "./createTempFile"; 7 | import {Cell} from "ton"; 8 | 9 | export async function compileFunc(source: string): Promise<{ fiftContent: string, cell: Cell }> { 10 | let sourceFile = await createTempFile('.fc') 11 | let fiftFile = await createTempFile('.fif') 12 | try { 13 | await writeFile(sourceFile.name, source) 14 | executeFunc(['-PS', '-o', fiftFile.name, sourceFile.name]) 15 | let fiftContent = await readFile(fiftFile.name, 'utf-8') 16 | fiftContent = fiftContent.slice(fiftContent.indexOf('\n') + 1) 17 | 18 | let codeCell = Cell.fromBoc(await compileFift(fiftContent))[0] 19 | 20 | return { fiftContent, cell: codeCell } 21 | } finally { 22 | await sourceFile.destroy() 23 | await fiftFile.destroy() 24 | } 25 | } -------------------------------------------------------------------------------- /packages/utils/createTempFile.ts: -------------------------------------------------------------------------------- 1 | import {uuid} from "./uuid"; 2 | import * as os from "os"; 3 | import path from "path"; 4 | import {writeFile, unlink} from "fs/promises"; 5 | 6 | export async function createTempFile(ext: string) { 7 | let name = uuid() 8 | let fullPath = path.resolve(os.tmpdir(), name + ext) 9 | await writeFile(fullPath, Buffer.alloc(0)) 10 | return { 11 | name: fullPath, 12 | destroy: async () => { 13 | await unlink(fullPath) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/utils/randomAddress.ts: -------------------------------------------------------------------------------- 1 | import {Address} from "ton"; 2 | import {pseudoRandomBytes} from "crypto"; 3 | 4 | export function randomAddress() { 5 | return new Address(0, pseudoRandomBytes(256/8)) 6 | } -------------------------------------------------------------------------------- /packages/utils/randomKeyPair.ts: -------------------------------------------------------------------------------- 1 | import {mnemonicNew, mnemonicToPrivateKey} from "ton-crypto"; 2 | 3 | export async function randomKeyPair() { 4 | let mnemonics = await mnemonicNew() 5 | return mnemonicToPrivateKey(mnemonics) 6 | } -------------------------------------------------------------------------------- /packages/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | import {v4} from 'uuid' 2 | 3 | export const uuid = () => v4() -------------------------------------------------------------------------------- /raffle.md: -------------------------------------------------------------------------------- 1 | # NFT Raffle contract 2 | 3 | The point of the contract is the possibility of a random exchange of NFTs. Each party can provide an arbitrary number of NFTs. 4 | 5 | ## Contract work concept 6 | 7 | Participant A and B want to randomly exchange their NFTs. The Marketplace or one of the participants can deploy a contract to randomly exchange NFTs. Both participants can verify the original data. 8 | 9 | 10 | ## Initialization 11 | 12 | 13 | ``` 14 | STORAGE TL-B 15 | 16 | state_info#_ state:uint2 right_nfts_count:uint4 right_nfts_received:uint4 17 | left_nfts_count:uint4 18 | left_nfts_received:uint4 = StateInfo; 19 | 20 | addr_info#_ left_user:MsgAddress right_user:MsgAddress super_user:MsgAddress = AddressInfo; 21 | 22 | commission_info#_ left_commission:Coins right_commission:Coins left_coins_got:Coins right_coins_got:Coins nft_transfer_fee:Coins marketplace_fee:Coins = CommissionInfo; 23 | 24 | dict_info#_ nfts:(HashmapE 256 uint4) raffled_nfts:(HashmapE 256 Bool) = DictInfo; 25 | 26 | storage#_ state:StateInfo addr:AddressInfo commissions:CommissionInfo dict:DictInfo = RaffleStorage; 27 | ``` 28 | 29 | Данная TL-B схема описывает storage контракта при деплое 30 | 31 | * `state` - raffle state `1` = active, `2` = cancelled, `3` = completed. Initially should be `1`. 32 | * `right nfts count` - the number of NFT right-hand side participant. 33 | * `right nfts received` - the number of NFTs received by the right-hand side participant, increases by `1` when NFT is received. Initially should be `0`. 34 | * `left nft count` - the number of NFT left-hand side participant. 35 | * `left nfts received` - the number of NFTs received by the left-hand side participant, increases by `1` when NFT is received. Initially should be `0`. 36 | 37 | * `left user` - is the address of the left-hand user. 38 | * `right user` - is the address of the right-hand user. 39 | * `super user` - is the address of the marketplace. 40 | 41 | * `left commission` - the left-hand participant's commission, determined by the formula `(sending commission for NFT + marketplace commission for NFT) * left-hand number of NFTs`. 42 | 43 | * `right commission` - the right-hand participant's commission, determined by the formula `(sending commission for NFT + marketplace commission for NFT) * right-hand number of NFTs`. 44 | 45 | * `left coins got` - the commission received by the left-handed participant, Initially should be `0`. 46 | 47 | * `right coins got` - the commission received by the right-handed participant, Initially should be `0`. 48 | 49 | * `nft transfer fee` - the commission for sending NFTs, which is used to send NFTs after a successful raffle. 50 | 51 | * `marketplace commission` - the commission for marketplace, marketplace receive it after successful raffle. 52 | 53 | * `nfts` - a dictionary that contains all addresses as keys, and their status as values. `0` - left nft, not received, `1` right nft, not received, `2` - left nft, received, `3` - right nft, received. 54 | 55 | * `raffled nfts` - a dictionary that contains addresses as keys, to whom they belong as values. `0` - left-hand user won this nft, `1` - right-hand user won this nft. 56 | 57 | 58 | ## Full work description 59 | 60 | After successfully contract deploying, each participant must send their NFTs. If all conditions are completed and the last NFT is received, all NFTs will be randomly raffled and transferred to their new owners. 61 | 62 | ### Cancel 63 | Both participants or the marketplace can cancel the raffle by sending a message. 64 | 65 | ``` 66 | TL-B 67 | cancel#000007D1 = Cancel; 68 | ``` 69 | Only if contract state = Active 70 | 71 | ### Maintain 72 | 73 | If something has gone wrong, the super user can send a message manually, only if contract state != Active 74 | 75 | ``` 76 | TL-B 77 | maintain#000007D3 msg:^Cell mode:uint8 = Maintain; 78 | ``` 79 | 80 | ### Add coins 81 | If not all conditions are met when sending the last NFT, e.g. one user has no money for the commission, he can add coins manually, only if contract state = active. The NFT raffle will start too. 82 | 83 | ``` 84 | TL-B 85 | add_coins#000007D2 = AddCoins; 86 | ``` 87 | 88 | ### Send Again 89 | If the NFTs are raffled but some NFTs require a special ton amount, the user can send a number of coins to the contract address and thereby initiate the sending of all NFTs again. Provided contract state = completed. 90 | ``` 91 | TL-B 92 | send_again#000007D4 = SendAgain; 93 | ``` -------------------------------------------------------------------------------- /sbt.md: -------------------------------------------------------------------------------- 1 | # SBT 2 | Soul bound token (SBT) is a special kind of NFT which can not be transferred. 3 | It includes optional certificate mechanics with revoke by authority and onchain ownership proofs. 4 | Holder can destroy his SBT in any time. 5 | 6 | #### Issuing (minting) 7 | Before mint, authority (collection owner) should verify wallet code offchain, and make sure that some tradeable contract is not used. 8 | 9 | #### Revoke 10 | Issuer can revoke SBT, using message with schema: 11 | ``` 12 | revoke#6f89f5e3 query_id:uint64 = InternalMsgBody 13 | ``` 14 | After that SBT will be marked as revoked and will have revoke time set. It can be checked using GET method `get_revoked_time` 15 | 16 | #### Changing owner's address 17 | Authority needs to send simple NFT transfer message with address of the new owner. 18 | It is useful in case if owner lost access to his wallet and needs to restore SBT, he could ask authority to transfer SBT to new account. 19 | Also using transfer, authority could revoke SBT, by transferring it to null address. 20 | 21 | It is also possible to destroy SBT, owner could do it by sending message to SBT with schema: 22 | ``` 23 | destroy#1f04537a query_id:uint64 = InternalMsgBody 24 | ``` 25 | 26 | After destroy owner and authority address will be cleared. 27 | 28 | #### Proving you ownership to contracts 29 | SBT contracts has a feature that let you implement interesting mechanics with contracts by proving ownership onchain. 30 | 31 | You can send message to SBT, and it will proxify message to target contract with its index, owner's address and initiator address in body, together with any useful for contract payload, 32 | this way target contract could know that you are owner of SBT which relates to expected collection. Contract could know that SBT relates to collection by calculating address of SBT using code and index, and comparing it with sender. 33 | 34 | There are 2 methods which allow to use this functionality, **ownership proof** and **ownership info**. 35 | The difference is that proof can be called only by SBT owner, so it is preferred to use when you need to accept messages only from owner, for example votes in DAO. 36 | 37 | ##### Ownership proof 38 | **SBT owner** can send message to SBT with this schema: 39 | ``` 40 | prove_ownership#04ded148 query_id:uint64 dest:MsgAddress 41 | forward_payload:^Cell with_content:Bool = InternalMsgBody; 42 | ``` 43 | After that SBT will send transfer to `dest` with scheme: 44 | ``` 45 | ownership_proof#0524c7ae query_id:uint64 item_id:uint256 owner:MsgAddress 46 | data:^Cell revoked_at:uint64 content:(Maybe ^Cell) 47 | ``` 48 | If something goes wrong and target contract not accepts message, and it will be bounced back to SBT, SBT will proxy this bounce to owner, this way coins will not stuck on SBT. 49 | 50 | ##### Ownership info 51 | **anyone** can send message to SBT with this schema: 52 | ``` 53 | request_owner#d0c3bfea query_id:uint64 dest:MsgAddress 54 | forward_payload:^Cell with_content:Bool = InternalMsgBody; 55 | ``` 56 | After that SBT will send transfer to `dest` with scheme: 57 | ``` 58 | owner_info#0dd607e3 query_id:uint64 item_id:uint256 initiator:MsgAddress owner:MsgAddress 59 | data:^Cell revoked_at:uint64 content:(Maybe ^Cell) 60 | ``` 61 | If something goes wrong and target contract not accepts message, and it will be bounced back to SBT, amount will stay on SBT. 62 | 63 | #### Verify SBT contract example 64 | 65 | ```C 66 | int op::ownership_proof() asm "0x0524c7ae PUSHINT"; 67 | 68 | int equal_slices (slice a, slice b) asm "SDEQ"; 69 | 70 | _ load_data() { 71 | slice ds = get_data().begin_parse(); 72 | 73 | return ( 74 | ds~load_msg_addr(), ;; collection_addr 75 | ds~load_ref() ;; sbt_code 76 | ); 77 | } 78 | 79 | slice calculate_sbt_address(slice collection_addr, cell sbt_item_code, int wc, int index) { 80 | cell data = begin_cell().store_uint(index, 64).store_slice(collection_addr).end_cell(); 81 | cell state_init = begin_cell().store_uint(0, 2).store_dict(sbt_item_code).store_dict(data).store_uint(0, 1).end_cell(); 82 | 83 | return begin_cell().store_uint(4, 3) 84 | .store_int(wc, 8) 85 | .store_uint(cell_hash(state_init), 256) 86 | .end_cell() 87 | .begin_parse(); 88 | } 89 | 90 | 91 | () recv_internal(int balance, int msg_value, cell in_msg_full, slice in_msg) impure { 92 | slice cs = in_msg_full.begin_parse(); 93 | int flags = cs~load_uint(4); 94 | 95 | slice sender_address = cs~load_msg_addr(); 96 | 97 | int op = in_msg~load_uint(32); 98 | int query_id = in_msg~load_uint(64); 99 | 100 | if (op == op::ownership_proof()) { 101 | int id = in_msg~load_uint(256); 102 | 103 | (slice collection_addr, cell sbt_code) = load_data(); 104 | throw_unless(403, equal_slices(sender_address, collection_addr.calculate_sbt_address(sbt_code, 0, id))); 105 | 106 | slice owner_addr = in_msg~load_msg_addr(); 107 | cell payload = in_msg~load_ref(); 108 | 109 | int revoked_at = in_msg~load_uint(64); 110 | throw_if(403, revoked_at > 0); 111 | 112 | int with_content = in_msg~load_uint(1); 113 | if (with_content != 0) { 114 | cell sbt_content = in_msg~load_ref(); 115 | } 116 | 117 | ;; 118 | ;; sbt verified, do something 119 | ;; 120 | 121 | return (); 122 | } 123 | 124 | throw(0xffff); 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /swap.md: -------------------------------------------------------------------------------- 1 | # Swap 2 | Contract for exchanging nft for nft, it is also supports exchanges of multiple nft and surcharge in toncoin. 3 | Contract supports marketplace commission, it can be included for any participant of exchange. 4 | 5 | Marketplace or one of swap participants should deploy contract with definition of conditions. 6 | Marketplace can guarantee similarity of conditions by precalculating contract address using data which was agreed by participants. 7 | 8 | When all conditions are met, swap will happen automatically. 9 | 10 | #### Initialization 11 | Contract initialization data is described by TL-B schema: 12 | ```tl-b 13 | marketplace_info#_ supervisor:MsgAddress commission:MsgAddress = SwapMarketplaceInfo; 14 | 15 | storage#_ state:(## 2) left_participant:MsgAddress right_participant:MsgAddress 16 | left_commission:Coins left_surcharge:Coins left_coins_got:Coins 17 | left_nft:(HashmapE 256 Bool) 18 | right_commission:Coins right_surcharge:Coins right_coins_got:Coins 19 | right_nft:(HashmapE 256 Bool) 20 | marketplace:^SwapMarketplaceInfo = SwapStorage; 21 | ``` 22 | This schema needs to be serialized to contract data in StateInit and deployed together with code. 23 | 24 | * `state` - swap state `1` = active, `2` = cancelled, `3` = completed. Initially should be `1`. 25 | * `(left|right)_participant` - address of user who will swap nft. 26 | * `(left|right)_commission` - amount of commission in nano ton that must be paid by user. 27 | * `(left|right)_surcharge` - amount of surcharge in nano ton that must be paid by user, will be transferred to other participant on swap complete. 28 | * `(left|right)_coins_got` - amount of coins in nano ton that was already paid by user. Initially should be `0`. 29 | * `(left|right)_nft` - dictionary which contains address's hash part of nft as key, and Bool which indicates nft owned by swap contract. Initially all values should be false (one zero bit) 30 | * `supervisor` - address of account which can do any transactions from swap contract, to resolve issues. 31 | * `commission` - address which will receive commission from swap. 32 | 33 | #### Exchange 34 | ##### NFT transfers 35 | After initialization, participants could transfer defined NFTs to swap contract address, together with 0.1 TON + desired commission+surcharge amount in forward amount of transfer message. 36 | 0.1 TON is required to pay contract fees for transferring nft back in case of cancel, or to another side in case of complete. **If forward amount is below 0.1, NFT will be ignored!** 37 | 38 | In case if undesirable nft was transferred to contract, it will be transferred back to previous owner (if forward amount >= 0.1). The same is applicable to undesired contract state, when swap is cancelled and someone transfers nft. 39 | 40 | ##### Commissions 41 | Normally, commission and surcharge should be transferred in forward amount of nft transfer, 42 | but it is also possible to send it independently, using message with schema: 43 | ```tl-b 44 | add_coins#00000001 query_id:uint64 commission:Coins = AddCoins; 45 | ``` 46 | 47 | ##### Completion 48 | Swap will happen when all nft will be transferred and each participant's commission + surcharge will be filled. 49 | 50 | On completion, each participant will receive transaction with return of amount of over-sent commission + surcharge with schema: 51 | ```tl-b 52 | completed#ef03d009 query_id:uint64 = CompletedNotification; 53 | ``` 54 | 55 | Sides will receive response from nft, with ~0.05 TON amount. 56 | 57 | And commission address will receive transaction with the rest contract balance amount, except 0.001 TON for storage, with schema: 58 | ```tl-b 59 | commission#82bd8f2a query_id:uint64 = CommissionNotification; 60 | ``` 61 | 62 | ##### Cancellation 63 | When contract is in active state and one of participants or supervisor decided to cancel exchange and get nft+commission+surcharge back, 64 | he should send message with schema: 65 | ```tl-b 66 | cancel#00000002 query_id:uint64 = Cancel; 67 | ``` 68 | 69 | On cancellation, each participant will receive transaction with return of amount of sent commission+surcharge, with schema: 70 | ```tl-b 71 | canceled#b5188860 query_id:uint64 = CanceledNotification; 72 | ``` 73 | 74 | Sides will receive response from their nft, with ~0.05 TON amount. 75 | 76 | And commission address will receive transaction with the rest contract balance amount, except 0.001 TON for storage, with schema: 77 | ```tl-b 78 | commission#82bd8f2a query_id:uint64 = CommissionNotification; 79 | ``` 80 | 81 | ##### Maintain 82 | In case of any issues, contract can be maintained by supervisor, he is eligible to send any transaction from the contract. 83 | For example return stuck NFT to one of participants in case of problem. 84 | 85 | To make transaction from contract, supervisor should send message with schema: 86 | ```tl-b 87 | maintain#00000003 query_id:uint64 mode:uint8 msg:^Cell = Cancel; 88 | ``` 89 | 90 | ##### Top up 91 | Useful for first deployment message or for some case, to trigger conditions check. 92 | 93 | To trigger conditions check, or to just add money to contract, anyone could send message with schema: 94 | ```tl-b 95 | topup#00000004 query_id:uint64 = TopUp; 96 | ``` 97 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "lib": [ 6 | "esnext" 7 | ], 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "sourceMap": false, 12 | "allowJs": false, 13 | "outDir": "build/packages", 14 | "esModuleInterop": true, 15 | "declaration": true, 16 | "strictNullChecks": true, 17 | "strict": true, 18 | "strictPropertyInitialization": false, 19 | "skipLibCheck": true, 20 | "paths": { 21 | "*": [ "./types/*", "./node_modules/@types"] 22 | } 23 | }, 24 | "exclude": [ 25 | "node_modules", 26 | "**/*.spec.ts" 27 | ], 28 | "include": [ 29 | "packages/**/*" 30 | ] 31 | } 32 | --------------------------------------------------------------------------------