easymina
Version:

312 lines (262 loc) • 11.9 kB
JavaScript
// import { PrivateKey } from 'o1js'
import { printMessages, keyPathToValue, shortenAddress } from './../helpers/mixed.mjs'
import fs from 'fs'
import moment from 'moment'
var PrivateKey
export class Contract {
#config
#state
constructor( { validate, networks, contracts, o1js } ) {
PrivateKey = o1js['PrivateKey']
this.#config = { validate, networks, contracts }
this.init()
}
init() {
this.#state = {}
}
async request( { name, contractAbsolutePath, networkName, deployer, encrypt, strict, contract, encryption, environment } ) {
const [ messages, comments ] = this.#validateState( { 'state': this.#state } )
printMessages( { messages, comments } )
const result = {
'header': {
'name': null,
'methods': [],
'projectName': null,
networkName,
'addressShort': null,
'addressFull': null,
'explorer': null,
'created': null,
'createdUnix': null,
'deployer': null,
'sourceCode': null,
encrypt
},
'body': {
'privateKey': {
'field': null,
'base58': null
},
'publicKey': {
'field': null,
'base58': null
}
},
'disclaimer': null
}
result['body']['privateKey']['base58'] = PrivateKey
.random()
.toBase58()
result['body']['privateKey']['field'] = PrivateKey
.fromBase58( result['body']['privateKey']['base58'] )
result['body']['publicKey']['field'] = result['body']['privateKey']['field']
.toPublicKey()
result['body']['publicKey']['base58'] = result['body']['publicKey']['field']
.toBase58()
result['header']['sourceCode'] = fs.readFileSync( contractAbsolutePath, 'utf-8' )
result['header']['deployer'] = deployer['filePath']
result['header']['projectName'] = environment
.getProjectNames()
.reduce( ( acc, projectName, index ) => {
const result = contractAbsolutePath
.split( '/' )
.find( a => ( a === projectName ) )
result !== undefined ? acc = result : ''
return acc
}, '' )
const contracts = environment.getDeployedContracts( {
contract,
encryption
} )
if( Object.hasOwn( contracts, result['header']['projectName'] ) ) {
if( Object.keys( contracts[ result['header']['projectName'] ] ).includes( name ) ) {
result['header']['name'] = `${name}-${moment().unix()}`
console.log( `Name '${name}' already taken, changed to '${result['header']['name']}'.` )
strict ? process.exit( 1 ) : ''
} else {
result['header']['name'] = name
}
} else {
result['header']['name'] = name
}
result['header']['methods'] = await environment
.getScriptMethods( { contractAbsolutePath } )
const mom = moment()
result['header']['created'] = mom.format( 'YYYY-MM-DD hh:mm:ss A' )
result['header']['createdUnix'] = mom.unix()
const publicKey = result['body']['publicKey']['base58']
result['header']['addressShort'] = shortenAddress( { publicKey } )
result['header']['addressFull'] = publicKey
result['header']['explorer'] = this.#config['networks'][ networkName ]['explorer']['wallet']
.replace( '{{publicKey}}', publicKey )
result['disclaimer'] = this.#config['contracts']['disclaimer']
this.#state['requestContract'] = result
return result
}
async prepareSave( { encryption } ) {
const [ messages, comments ] = this.#validateState( { 'state': this.#state } )
printMessages( { messages, comments } )
let contract = this.#state['requestContract']
let credential = this.#state['requestContract']
credential['body'] = {
'account': {
'publicKey': credential['body']['publicKey']['base58'],
'privateKey': credential['body']['privateKey']['base58'],
}
}
credential = encryption.encryptCredential( { credential } )
return credential
}
getDeployedContract( { filePath, encryption } ) {
const raw = fs.readFileSync( filePath )
let credential = JSON.parse( raw )
credential = encryption.decryptCredential( { credential } )
const result = {
'privateKey': {
'base58': null,
'field': null
},
'publicKey': {
'base58': null,
'field': null
}
}
result['privateKey']['base58'] = credential['body']['account']['privateKey']
result['privateKey']['field'] = PrivateKey
.fromBase58( result['privateKey']['base58'] )
result['publicKey']['field'] = result['privateKey']['field']
.toPublicKey()
result['publicKey']['base58'] = result['publicKey']['field']
.toBase58()
return result
}
#validateState( { state } ) {
const messages = []
const comments = []
if( typeof state === 'undefined' ) {
messages.push( `Class 'Contract' the variable 'state' is not initialized. Run .init() before.` )
} else if (typeof state !== 'object' || state === null || Array.isArray( state ) ) {
messages.push( `Class 'Contact' the variable 'state' is not type of '{}'.` )
}
return [ messages, comments ]
}
validateRequest( { name, contractAbsolutePath, networkName, deployer, encrypt } ) {
const messages = []
const comments = []
if( typeof name !== 'string' ) {
messages.push( `Key 'name' is not type of 'string'.` )
} else if( !this.#config['validate']['values']['stringsAndDash']['regex'].test( name ) ) {
messages.push( `Key 'name' with the value '${name}' has not the expected pattern. ${this.#config['validate']['values']['stringsAndDash']['description']}` )
}
if( contractAbsolutePath === null ) {
comments.push( `Key 'sourceCode' is empty, will ignored.` )
} else if( typeof contractAbsolutePath !== 'string' ) {
messages.push( `Key 'sourceCode' is not type of 'string'.` )
} else if( !fs.existsSync( contractAbsolutePath ) ) {
messages.push( `Key 'sourceCode' provided path '${contractAbsolutePath}' is not valid.` )
}
if( typeof networkName !== 'string' ) {
messages.push( `Key 'networkName' is not type of 'string'.` )
} else if( networkName === '' ) {
messages.push( `Key 'networkName' can not be ''. Supported networks are ${this.#config['networks']['supported'].map( a => `'${a}'`).join( ', ' )}.` )
} else if( !this.#config['networks']['supported'].includes( networkName ) ) {
messages.push( `Key 'networkName' with the value '${networkName}' is not a currently supported network. Supported networks are ${this.#config['networks']['supported'].map( a => `'${a}'`).join( ', ' )}.` )
}
if( typeof encrypt !== 'boolean' ) {
messages.push( `Key 'encrypt' is not type of 'boolean'. ` )
}
if( typeof deployer !== 'object' ) {
messages.push( `Key 'deployer' is not type of 'object'.` )
}
return [ messages, comments ]
}
validateCredential( { filePath, encryption } ) {
const messages = []
const comments = []
let msg = ''
let json
try {
msg = `FilePath '${filePath}' could not load file`
const txt = fs.readFileSync( filePath, 'utf-8' )
msg = `FilePath '${filePath}' is not parsable.`
json = JSON.parse( txt )
} catch( e ) {
console.log( e )
messages.push( msg )
}
if( messages.length === 0 ) {
json = encryption.decryptCredential( {
'credential': json
} )
const tests = this.#config['validate']['files']['contract']['keys']
.map( a => {
const { name, key, validation, type } = a
const value = keyPathToValue( { 'data': json, 'keyPath': key } )
const regex = keyPathToValue( { 'data': this.#config, 'keyPath': validation } )
let test = null
switch( type ) {
case 'string':
if( typeof value === undefined ) {
test = false
messages.push(`FilePath '${filePath}', key '${name}' is 'undefined'.` )
} else if( typeof value !== 'string' ) {
test = false
messages.push( `FilePath '${filePath}', key '${name}' is type of 'string'.` )
} else if( !regex['regex'].test( `${value}` ) ) {
test = false
messages.push( `FilePath '${filePath}', key '${name}' is not a valid pattern. ${regex['description']}` )
} else {
test = true
}
break
case 'array':
if( !Array.isArray( value ) ) {
messages.push( `FilePath '${filePath}', key '${name}' is type of 'array'.` )
test = false
} else {
test = value
.map( v => regex['regex'].test( v ) )
.every( a => a )
if( !test ) {
messages.push( `FilePath '${filePath}', key '${name}' is not a valid pattern. ${regex['description']}` )
}
}
break
default:
console.log( `Unknown Type: ${type}.` )
break
}
return test
} )
.every( a => a )
if( !tests && messages.length === 0 ) {
messages.push( `Credential raised an error.` )
}
const publicKey = PrivateKey
.fromBase58( json['body']['account']['privateKey'] )
.toPublicKey()
.toBase58()
const tests2 = [
[
publicKey,
json['body']['account']['publicKey']
],
[
shortenAddress( { publicKey } ),
json['header']['addressShort']
],
[
this.#config['networks'][ json['header']['networkName'] ]['explorer']['wallet']
.replace( `{{publicKey}}`, publicKey ),
json['header']['explorer']
]
]
.map( a => a[ 0 ] === a[ 1 ] )
.every( a => a )
if( !tests2 ) {
messages.push( `PrivateKey from bodies privateKey differs from header publickey.` )
}
}
return [ messages, comments ]
}
}