import { my , Storage , BaseContract } from '@antchain/myassembly' ; import { Address , Attribute , AttributeType , Collection } from './types' ; import { parseHex2Int , stringifyCollection , stringifyCollections } from './utils' ; export default class CryptoFishContract extends BaseContract { public ruleHash : string = 'ada24c6d6d4d403374c81995cceb7262' ; public standard : string = 'CryptoFish' ; private limit : u32 ; private canMint : Storage < bool > = new Storage ( 'canMint' , true ) ; private owner : Storage < Address > = new Storage ( 'owner' , '' ) ; private collections : Storage < Collection [ ] > = new Storage ( 'collections' , [ ] ) ; private collectionAttributeMap : Storage < Map < Attribute , bool > > = new Storage ( 'collectionAttributeMap' , new Map < Attribute , bool > ( ) ) ; private logo : Storage < Collection > = new Storage ( 'logo' , new Map < string , string > ( ) ) ; private isLogoPicked : Storage < bool > = new Storage ( 'isLogoPicked' , false ) ; private attributeKeyList : AttributeType [ ] = [ 'skin' , 'background' , 'frame' , 'fin' , 'eye' , 'tail' ] ; private attributeWeights : Map < AttributeType , u32 > = new Map < AttributeType , u32 > ( ) ; private attributes : Map < AttributeType , string > = new Map < AttributeType , string > ( ) ; constructor ( ) { super ( ) ; this . limit = 20 ; this . attributeWeights . set ( 'skin' , 330 ) ; this . attributeWeights . set ( 'background' , 220 ) ; this . attributeWeights . set ( 'frame' , 220 ) ; this . attributeWeights . set ( 'fin' , 110 ) ; this . attributeWeights . set ( 'eye' , 100 ) ; this . attributeWeights . set ( 'tail' , 100 ) ; this . attributes . set ( 'skin' , '0123456789' ) ; this . attributes . set ( 'background' , '0123456789AB' ) ; this . attributes . set ( 'frame' , '0123456789AB' ) ; this . attributes . set ( 'fin' , '0123456789' ) ; this . attributes . set ( 'eye' , '0123456789' ) ; this . attributes . set ( 'tail' , '0123456789' ) ; } public onContractDeploy ( ) { if ( this . owner . get ( ) . length > 0 ) { throw new Error ( '[cryptofish] cannot run this method' ) ; } const ownerAddress = my . getSender ( ) . toString ( ) ; this . owner . set ( ownerAddress ) ; this . log ( `contract created by: ${ ownerAddress }` ) ; this . setCanMint ( 1 ) ; this . mint ( ) ; } public mint ( ) { const creator = my . getSender ( ) . toString ( ) ; const ownedCount = < u32 > this . getOwnedCollectionsPrivate ( ) . length ; const canMint = this . canMint . get ( ) ; if ( ownedCount >= this . limit ) { this . log ( `error: you cannot own more than ${ this . limit } collections(${ creator })` ) ; return 'null' ; } if ( ! canMint ) { this . log ( `error: ${ this . standard } minting is not available` ) ; return 'null' ; } const attribute = this . generateUniqAttribute ( ) ; const collections = this . collections . get ( ) ; const collectionAttributeMap = this . collectionAttributeMap . get ( ) ; const index = collections . length ; const collection = new Map < string , string > ( ) ; collection . set ( 'index' , index . toString ( ) ) ; collection . set ( 'creator' , creator ) ; collection . set ( 'attribute' , attribute ) ; collection . set ( 'score' , this . calculateScore ( attribute ) . toString ( ) ) ; collection . set ( 'favorCount' , '0' ) ; this . log ( 'mint collection success:' ) ; this . printCollection ( collection ) ; collections . push ( collection ) ; this . collections . set ( collections ) ; collectionAttributeMap . set ( attribute , true ) ; this . collectionAttributeMap . set ( collectionAttributeMap ) ; return stringifyCollection ( collection ) ; } public setCanMint ( canMint ) { if ( this . isOwner ( ) ) { this . canMint . set ( canMint === 0 ? false : true ) ; } } public pickLogoByScore ( ) { if ( ! this . isOwner ( ) ) { this . log ( 'error: you do not have permission to pick logo' ) ; return false ; } const isLogoPicked = this . isLogoPicked . get ( ) ; if ( isLogoPicked ) { const logo = this . logo . get ( ) ; this . log ( `error: logo has been picked:` ) ; this . printCollection ( logo ) ; return false ; } let logo ! ; let logoScore = 0 ; const collections = this . collections . get ( ) ; for ( let index = 0 ; index < collections . length ; index += 1 ) { const collection = collections [ index ] ; if ( ! collection || ! collection . get ( 'index' ) ) continue ; const currentScore = < u32 > parseInt ( collection . get ( 'score' ) , 10 ) ; if ( currentScore > logoScore ) { logo = collection ; logoScore = currentScore ; } } this . logo . set ( logo ) ; this . isLogoPicked . set ( true ) ; this . log ( `pickLogoByScore success(${ logoScore })` ) ; this . printCollection ( logo ) ; return true ; } public getLogo ( ) { const isLogoPicked = this . isLogoPicked . get ( ) ; if ( ! isLogoPicked ) { throw new Error ( '[cryptofish] logo has not been picked, please wait' ) ; } const logo = this . logo . get ( ) ; this . log ( 'get logo success:' ) ; this . printCollection ( logo ) ; return stringifyCollection ( logo ) ; } public favorByIndex ( index ) { return this . favorCollectionByIndex ( index , this . getCollectionByIndexPrivate ( index ) ) ; } public favorByAttribute ( attribute ) { const collections = this . collections . get ( ) ; let idx ! ; let collection ! ; for ( let index = 0 ; index < collections . length ; index += 1 ) { const current = collections [ index ] ; if ( current . get ( 'attribute' ) == attribute ) { idx = index ; collection = current ; break ; } } return this . favorCollectionByIndex ( idx , collection ) ; } private favorCollectionByIndex ( index , collection ) { if ( ! collection . get ( 'index' ) ) return false ; const address = my . getSender ( ) . toString ( ) ; if ( collection . get ( 'creator' ) == address && ! this . isOwner ( ) ) return false ; const favorCount = parseInt ( collection . get ( 'favorCount' ) , 10 ) ; collection . set ( 'favorCount' , ( < u32 > ( favorCount + 1 ) ) . toString ( ) ) ; this . updateCollectionByIndex ( index , collection ) ; return true ; } private updateCollectionByIndex ( index , collection ) { const collections = this . collections . get ( ) ; collections [ < i32 > index ] = collection ; this . collections . set ( collections ) ; } public getCollectionByIndex ( index ) { return stringifyCollection ( this . getCollectionByIndexPrivate ( index ) ) ; } public getCollectionByIndexPrivate ( index ) { const collections = this . collections . get ( ) ; const collection = collections [ index ] ; this . log ( `getCollectionByIndex(${ index }) =>` ) ; this . printCollection ( collection ) ; return collection ; } public getCollectionByAttribute ( attribute ) { return stringifyCollection ( this . getCollectionByAttributePrivate ( attribute ) ) ; } public getCollectionByAttributePrivate ( attribute ) { const collections = this . collections . get ( ) ; let collection ! ; for ( let index = 0 ; index < collections . length ; index += 1 ) { const current = collections [ index ] ; if ( current . get ( 'attribute' ) == attribute ) { collection = current ; break ; } } this . log ( `getCollectionByAttribute(${ attribute }) =>` ) ; this . printCollection ( collection ) ; return collection ; } public getOwnedCollections ( ) { return stringifyCollections ( this . getOwnedCollectionsPrivate ( ) ) ; } public getOwnedCollectionsPrivate ( ) { const address = my . getSender ( ) . toString ( ) ; const totalCollections = this . collections . get ( ) ; const collections = [ ] ; for ( let index = 0 ; index < totalCollections . length ; index += 1 ) { const collection = totalCollections [ index ] ; if ( collection . get ( 'creator' ) != address ) continue ; collections . push ( collection ) ; } this . log ( `getOwnedCollections: ${ collections . length }` ) ; this . printCollections ( collections ) ; return collections ; } public getCollectionCount ( ) { return < u32 > this . collections . get ( ) . length ; } public getCollections ( limit , skip ) { return stringifyCollections ( this . getCollectionsPrivate ( limit , skip ) ) ; } public getCollectionsPrivate ( limit , skip ) { const collections = this . collections . get ( ) ; const collectionsPart = collections . slice ( skip , skip + limit ) ; this . printCollections ( collectionsPart ) ; return collectionsPart ; } private generateUniqAttribute ( ) { const txHash = my . getTxHash ( ) ; const skinRange = this . attributes . get ( 'skin' ) . length ; const backgroundRange = this . attributes . get ( 'background' ) . length ; const frameRange = this . attributes . get ( 'frame' ) . length ; const finRange = this . attributes . get ( 'fin' ) . length ; const eyeRange = this . attributes . get ( 'eye' ) . length ; const tailRange = this . attributes . get ( 'tail' ) . length ; for ( let index = 0 ; index < txHash . length - 6 ; index += 1 ) { const sourceAttribute = txHash . substr ( index , 6 ) . split ( '' ) ; const skin = parseHex2Int ( sourceAttribute [ 0 ] ) % skinRange ; const background = parseHex2Int ( sourceAttribute [ 1 ] ) % backgroundRange ; const frame = parseHex2Int ( sourceAttribute [ 2 ] ) % frameRange ; const fin = parseHex2Int ( sourceAttribute [ 3 ] ) % finRange ; const eye = parseHex2Int ( sourceAttribute [ 4 ] ) % eyeRange ; const tail = parseHex2Int ( sourceAttribute [ 5 ] ) % tailRange ; const attribute = `${ skin . toString ( ) }${ background . toString ( ) }${ frame . toString ( ) }${ fin . toString ( ) }${ eye . toString ( ) }${ tail . toString ( ) }` ; if ( this . isAttributeAvailable ( attribute ) ) return attribute ; } throw new Error ( '[cryptofish] cannot generate attribute' ) ; } private calculateScore ( attribute ) { const attrStrList = attribute . split ( '' ) ; const attrU32List = attrStrList . map < u32 > ( ( hex ) => parseHex2Int ( hex ) ) ; let score = 0 ; for ( let index = 0 ; index < attrU32List . length ; index ++ ) { const currentScore = attrU32List [ index ] ; const weight = this . attributeWeights . get ( this . attributeKeyList [ index ] ) || 100 ; score += currentScore * weight ; } return score ; } private isAttributeAvailable ( attribute ) { const collectionAttributeMap = this . collectionAttributeMap . get ( ) ; if ( collectionAttributeMap . has ( attribute ) && collectionAttributeMap . get ( attribute ) === true ) { return false ; } return true ; } private isOwner ( ) { return this . owner . get ( ) == my . getSender ( ) . toString ( ) ; } private printCollections ( c ) { for ( let index = 0 ; index < c . length ; index += 1 ) { this . printCollection ( c [ index ] ) ; } } private printCollection ( c ) { const index = c . get ( 'index' ) ; if ( ! index ) { this . log ( 'error: collection not found' ) ; } const creator = c . get ( 'creator' ) ; const attribute = c . get ( 'attribute' ) ; const score = c . get ( 'score' ) ; const favorCount = c . get ( 'favorCount' ) ; this . log ( `Collection{index:${ index }, creator:"${ creator }", attribute:"${ attribute }", score:${ score }, favor:${ favorCount }}` ) ; } private log ( message ) { my . log ( `[cryptofish] ${ message }` , [ ] ) ; } }