UNPKG

@velas/account-agent

Version:

sdk

323 lines (260 loc) 14.6 kB
import { Keccak } from 'sha3'; import { uniqueNamesGenerator, adjectives, animals } from 'unique-names-generator'; import { SolidInstruction, SignerType, RequestedOpt } from './instructions' import ErrorHendler from './error'; import ScopesConstructor from './scopes'; import { Vaccount, OperationalStruct, Initialized } from './account-schema' const errorHendler = new ErrorHendler('AccountConstructor'); function AccountConstructor(options = {}) { if (!options.connection) throw new Error('connection option is required') if (!options.account_contract) throw new Error('account_contract option is required') if (!options.PublicKey) throw new Error('PublicKey module is required') this.connection = options.connection; this.PublicKey = options.PublicKey; this.sc = new ScopesConstructor({ account_contract: options.account_contract }); }; AccountConstructor.prototype.accountEvmAddressToReadable = function(address) { if (typeof address !== 'string') throw new Error('address should be string'); return uniqueNamesGenerator({ style: 'capital', separator: ' ', seed: parseInt(address.slice(-8), 16), dictionaries: [adjectives, adjectives, animals], }); }; AccountConstructor.prototype.accountPublicKeyToEVMAddress = function(accountPublicKey) { accountPublicKey = typeof accountPublicKey === 'string' ? new this.PublicKey(accountPublicKey) : accountPublicKey; const hash = new Keccak(256); const accountPublicKeyHash = hash.update(accountPublicKey.toBuffer()).digest(); return "0x" + Buffer.concat([ Buffer.from([0xAC, 0xC0]), accountPublicKeyHash.slice(14, 32) ]).toString("hex"); }; AccountConstructor.prototype.getStorageAddress = async function(storageType, accountPublicKey, version) { const publicKey = await this.PublicKey.findProgramAddress([ accountPublicKey.toBuffer(), Buffer.from(Buffer.concat([Buffer.from(storageType), Buffer.from(Uint16Array.of(version).buffer) ])), ], new this.PublicKey(this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS)); return publicKey[0]; }; AccountConstructor.prototype.toVelasAccountScopes = function(scopes = []) { scopes = scopes.reduce((result, item) => { if (item.split(':')[0] === this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS) { result.push(item.split(':')[1]); }; return result; },[]); return Uint8Array.of(...scopes); }; AccountConstructor.prototype.getAccountPrograms = async function(accountData) { const programsStorageCurrentIndex = accountData.storage.programs_current_index; const programsStorageAddress = await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex); const programsStorage = await this.connection.getAccountInfo(programsStorageAddress); const buff = programsStorage ? programsStorage.data : []; return this.sc.chunkArray(buff, 33).reduce((acc, curr, i) => { if (i === 0) acc = []; const publickKey = new this.PublicKey(curr); const publickKeyBase58 = publickKey.toBase58(); acc.push(publickKeyBase58); return acc; }, {}); }; AccountConstructor.prototype.getParsedOperationalStorage = async function(operationalStorageNonce, vaccountAddress) { const operationalsStorageAddress = await this.getStorageAddress('operationals', vaccountAddress, operationalStorageNonce); const operationalsStorageInfo = await this.connection.getAccountInfo(operationalsStorageAddress); const operationals = []; if (operationalsStorageInfo) { const number_operational = operationalsStorageInfo.data.length / OperationalStruct.len(); // TO DO: check for (let i = 0; i < number_operational; i++) { const start = i * OperationalStruct.len(); operationals.push(OperationalStruct.decode(operationalsStorageInfo.data.slice(start, start + OperationalStruct.len()))); }; }; return operationals; }; AccountConstructor.prototype.toWhiteList = async function(scopes = [], accountData) { const programsStorageCurrentIndex = accountData.storage.programs_current_index; const programsStorageAddress = await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex) const programsStorage = await this.connection.getAccountInfo(programsStorageAddress); const programsStorageData = programsStorage ? programsStorage.data : []; const programsStorageDataSplited = this.sc.chunkArray(programsStorageData, 33) const programsInScopes = []; scopes.filter(item => { if (this.sc.PERMITTED_SCOPES.includes(item)) return false; if (item.split(':')[0] === this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS) return false; return true; }).forEach(item => { programsInScopes.push(new this.PublicKey(item.split(':')[0])); }); let whitelistProgramsIndexes = []; let whitelistPrograms = [] let uniqueProgramsInScopes = [...new Set(programsInScopes)]; for (const [uniqueProgramIndex, program] of uniqueProgramsInScopes.entries()) { let index = undefined; for (const i in programsStorageDataSplited) { const address = new this.PublicKey(programsStorageDataSplited[i]); if (address.toBase58() === program.toBase58()) index = i; }; if (index) { whitelistProgramsIndexes.push(index); } else { whitelistProgramsIndexes.push(programsStorageDataSplited.length + uniqueProgramIndex); whitelistPrograms.push({ pubkey: program, current: await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex), //next: await this.getStorageAddress('programs', accountData.public_key, programsStorageCurrentIndex + whitelistPrograms.length + 1), current_index: programsStorageCurrentIndex + whitelistPrograms.length, next_index: programsStorageCurrentIndex + whitelistPrograms.length + 1 }); }; }; return { scopes: this.toVelasAccountScopes(scopes), whitelistPrograms, whitelistProgramsIndexes, }; }; AccountConstructor.prototype.removeOperationalData = async function(accountData, publicKeyOperationalToRemove) { const external_programs_indices = this.sc.bitsToScopes(accountData.operational_keys[publicKeyOperationalToRemove].external_programs_indices, 256); return { storage: { programs_current: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index), programPermissionsToRemove: external_programs_indices.map(item => { return item; }) }, }; }; AccountConstructor.prototype.executeTransactionData = async function(accountData) { return { storage: { programs_current: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index), }, }; }; AccountConstructor.prototype.addOperationalData = async function(accountData, new_op_key_params, signerType) { const isMasterKey = new_op_key_params.scopes.length ? 0 : 1; const { scopes, whitelistPrograms, whitelistProgramsIndexes, } = await this.toWhiteList(new_op_key_params.scopes, accountData); const operationalKeyData = { signerType: signerType || SignerType.owner(), requestedOption: new RequestedOpt({ agentType: Buffer.from(new_op_key_params.agentType).slice(0,32), scopes, whitelistProgramsIndexes, whitelistTokensIndices: new Uint8Array, isMasterKey, }) }; if(new_op_key_params.extend){ const operationalToExtendIndex = Object.keys(accountData.operational_keys).indexOf(new_op_key_params.pubkey.toBase58()) operationalKeyData.operationalToExtendIndex = operationalToExtendIndex } let lamports = 0; if (accountData.ephemeral ) { lamports = lamports + await this.connection.getMinimumBalanceForRentExemption(135) // account storage lamports = lamports + await this.connection.getMinimumBalanceForRentExemption(134) // operational storage } else { lamports = lamports + (await this.connection.getMinimumBalanceForRentExemption(134 + 134) - await this.connection.getMinimumBalanceForRentExemption(134)); }; if (whitelistPrograms.length && accountData.storage.programs_current_index === 1) { // programs storage lamports = lamports + await this.connection.getMinimumBalanceForRentExemption(33 * whitelistPrograms.length); } else if (whitelistPrograms.length && accountData.storage.programs_current_index !== 1) { const cur_programs_len = accountData.storage.programs_current_index - 1; const new_programs_len = cur_programs_len + whitelistPrograms.length; const cur_lamports = await this.connection.getMinimumBalanceForRentExemption(33 * cur_programs_len); const new_lamports = await this.connection.getMinimumBalanceForRentExemption(33 * new_programs_len); const need_to_add = new_lamports - cur_lamports; lamports = lamports + need_to_add; }; // TO DO: add lamports for tokens return { storage: { whitelistPrograms, rent: lamports, addOperationalEncoded: SolidInstruction.addOperational(operationalKeyData).encode(), extendOperationalScopesEncoded: SolidInstruction.extendOperational(operationalKeyData).encode(), addProgramEncoded: SolidInstruction.addProgram(operationalKeyData).encode(), programs_current: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index), programs_next: await this.getStorageAddress('programs', accountData.public_key, accountData.storage.programs_current_index + whitelistPrograms.length), tokens_current: await this.getStorageAddress('tokens', accountData.public_key, accountData.storage.tokens_current_index), tokens_next: await this.getStorageAddress('tokens', accountData.public_key, accountData.storage.tokens_current_index), }, }; }; AccountConstructor.prototype.getAccountData = async function(accountPublicKey, ephemeral = false, merge = false) { let account = await this.connection.getAccountInfo(accountPublicKey); if ( !account && !ephemeral ) return undefined; const parsed = { public_key: accountPublicKey, account_key: accountPublicKey.toBase58(), account_key_evm: this.accountPublicKeyToEVMAddress(accountPublicKey), instruction: { initializeEncoded: SolidInstruction.initialize().encode(), addOwnerEncoded: SolidInstruction.addOwner().encode(), replaceOwnerEncoded: SolidInstruction.replaceOwner().encode(), }, storage: { storage_current: await this.getStorageAddress('operationals', accountPublicKey, 1), // storage_next: await this.getStorageAddress('operationals', accountPublicKey, 2), programs_current_index: 1, tokens_current_index: 1, }, }; const accountOwner = account ? account.owner.toBase58() : this.sc.VELAS_ACCOUNT_PROGRAM_ADDRESS; if ((!account || accountOwner === this.sc.SYSTEM_PROGRAM_ADDRESS) && ephemeral ) { parsed.ephemeral = true; return parsed; } else if(accountOwner === this.sc.SYSTEM_PROGRAM_ADDRESS && !ephemeral ) { return undefined; }; if (account) { // Check if account was preinitialized const usefulData = account.data.filter(i => i !== 0); if (!usefulData.length) parsed.ephemeral = true; const info = Vaccount.decode(account.data); account = { value: { data: { parsed: { info }}}, owner: account.owner, }; account.value.data.parsed.info.owners = this.sc.chunkArray(account.value.data.parsed.info.owners, 32); }; try { parsed.storage = { storage_current: await this.getStorageAddress('operationals', accountPublicKey, account.value.data.parsed.info.operational_storage_nonce), // storage_next: await this.getStorageAddress('operationals', accountPublicKey, merge ? account.value.data.parsed.info.operational_storage_nonce - 1 : account.value.data.parsed.info.operational_storage_nonce + 1), programs_current_index: account.value.data.parsed.info.programs_storage_nonce, tokens_current_index: account.value.data.parsed.info.token_storage_nonce, }; } catch (error) { errorHendler.error("storage processing error", error) }; const storage = await this.getParsedOperationalStorage(account.value.data.parsed.info.operational_storage_nonce, accountPublicKey ); try { parsed.operational_keys = storage.reduce((acc, curr, index) => { const agent_type = Array.from(curr.agent_type).splice(0, Array.from(curr.agent_type).indexOf(0)) const publickKey = new this.PublicKey(curr.pubkey.bytes); curr.is_master_key = !!curr.is_master_key; curr.index = index; curr.pubkey = publickKey.toBase58(); curr.agent_type = String.fromCharCode.apply(String, agent_type); curr.scopes = this.sc.bitsToScopes(curr.scopes); curr.active = curr.state.initialize instanceof Initialized; acc[curr.pubkey] = curr; return acc; }, {}); } catch (error) { errorHendler.error("op_keys storage processing error", error) }; try { parsed.owner_keys = account.value.data.parsed.info.owners.reduce((acc, curr, i) => { if (i === 0) acc = []; const publickKey = new this.PublicKey(curr); const publickKeyBase58 = publickKey.toBase58(); if (publickKeyBase58 !== this.sc.SYSTEM_PROGRAM_ADDRESS) acc.push(publickKeyBase58); return acc; }, {}); } catch (error) { errorHendler.error("owner_keys storage processing error", error) }; return parsed; }; export default AccountConstructor;