/************************************************************************* * Copyright 2022 Wormhole Project Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const sha512 = require('js-sha512') const hibase32 = require('hi-base32') const ALGORAND_ADDRESS_SIZE = 58 function timeoutPromise (ms, promise) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error('promise timeout')) }, ms) promise.then( (res) => { clearTimeout(timeoutId) resolve(res) }, (err) => { clearTimeout(timeoutId) reject(err) } ) }) } function getInt64Bytes (x, len) { if (!len) { len = 8 } const bytes = new Uint8Array(len) do { len -= 1 // eslint-disable-next-line no-bitwise bytes[len] = x & (255) // eslint-disable-next-line no-bitwise x >>= 8 } while (len) return bytes } function addressFromByteBuffer (addr) { const bytes = Buffer.from(addr, 'base64') // compute checksum const checksum = sha512.sha512_256.array(bytes).slice(28, 32) const c = new Uint8Array(bytes.length + checksum.length) c.set(bytes) c.set(checksum, bytes.length) const v = hibase32.encode(c) return v.toString().slice(0, ALGORAND_ADDRESS_SIZE) } function printAppCallDeltaArray (deltaArray) { for (let i = 0; i < deltaArray.length; i++) { if (deltaArray[i].address) { console.log('Local state change address: ' + deltaArray[i].address) for (let j = 0; j < deltaArray[i].delta.length; j++) { printAppCallDelta(deltaArray[i].delta[j]) } } else { console.log('Global state change') printAppCallDelta(deltaArray[i]) } } } function printAppStateArray (stateArray) { for (let n = 0; n < stateArray.length; n++) { printAppState(stateArray[n]) } } function appValueState (stateValue) { let text = '' if (stateValue.type == 1) { const addr = addressFromByteBuffer(stateValue.bytes) if (addr.length == ALGORAND_ADDRESS_SIZE) { text += addr } else { text += stateValue.bytes } } else if (stateValue.type == 2) { text = stateValue.uint } else { text += stateValue.bytes } return text } function appValueStateString (stateValue) { let text = '' if (stateValue.type == 1) { const addr = addressFromByteBuffer(stateValue.bytes) if (addr.length == ALGORAND_ADDRESS_SIZE) { text += addr } else { text += stateValue.bytes } } else if (stateValue.type == 2) { text += stateValue.uint } else { text += stateValue.bytes } return text } function printAppState (state) { let text = Buffer.from(state.key, 'base64').toString() + ': ' text += appValueStateString(state.value) console.log(text) } async function printAppLocalState (algodClient, appId, accountAddr) { const ret = await readAppLocalState(algodClient, appId, accountAddr) if (ret) { console.log('Application %d local state for account %s:', appId, accountAddr) printAppStateArray(ret) } } async function printAppGlobalState (algodClient, appId, accountAddr) { const ret = await readAppGlobalState(algodClient, appId, accountAddr) if (ret) { console.log('Application %d global state:', appId) printAppStateArray(ret) } } async function readCreatedApps (algodClient, accountAddr) { const accountInfoResponse = await algodClient.accountInformation(accountAddr).do() return accountInfoResponse['created-apps'] } async function readOptedInApps (algodClient, accountAddr) { const accountInfoResponse = await algodClient.accountInformation(accountAddr).do() return accountInfoResponse['apps-local-state'] } // read global state of application async function readAppGlobalState (algodClient, appId, accountAddr) { const accountInfoResponse = await algodClient.accountInformation(accountAddr).do() for (let i = 0; i < accountInfoResponse['created-apps'].length; i++) { if (accountInfoResponse['created-apps'][i].id === appId) { const globalState = accountInfoResponse['created-apps'][i].params['global-state'] return globalState } } } async function readAppGlobalStateByKey (algodClient, appId, accountAddr, key) { const accountInfoResponse = await algodClient.accountInformation(accountAddr).do() for (let i = 0; i < accountInfoResponse['created-apps'].length; i++) { if (accountInfoResponse['created-apps'][i].id === appId) { // console.log("Application's global state:") const stateArray = accountInfoResponse['created-apps'][i].params['global-state'] for (let j = 0; j < stateArray.length; j++) { const text = Buffer.from(stateArray[j].key, 'base64').toString() if (key === text) { return appValueState(stateArray[j].value) } } } } } // read local state of application from user account async function readAppLocalState (algodClient, appId, accountAddr) { const accountInfoResponse = await algodClient.accountInformation(accountAddr).do() for (let i = 0; i < accountInfoResponse['apps-local-state'].length; i++) { if (accountInfoResponse['apps-local-state'][i].id === appId) { // console.log(accountAddr + " opted in, local state:") if (accountInfoResponse['apps-local-state'][i]['key-value']) { return accountInfoResponse['apps-local-state'][i]['key-value'] } } } } async function readAppLocalStateByKey (algodClient, appId, accountAddr, key) { const accountInfoResponse = await algodClient.accountInformation(accountAddr).do() for (let i = 0; i < accountInfoResponse['apps-local-state'].length; i++) { if (accountInfoResponse['apps-local-state'][i].id === appId) { const stateArray = accountInfoResponse['apps-local-state'][i]['key-value'] if (!stateArray) { return null } for (let j = 0; j < stateArray.length; j++) { const text = Buffer.from(stateArray[j].key, 'base64').toString() if (key === text) { return appValueState(stateArray[j].value) } } // not found assume 0 return 0 } } } function uintArray8ToString (byteArray) { return Array.from(byteArray, function (byte) { // eslint-disable-next-line no-bitwise return ('0' + (byte & 0xFF).toString(16)).slice(-2) }).join('') } /** * Verify if transactionResponse has any information about a transaction local or global state change. * @param {Object} transactionResponse object containing the transaction response of an application call * @return {Boolean} returns true if there is a local or global delta meanining that * the transaction made a change in the local or global state */ function anyAppCallDelta (transactionResponse) { return (transactionResponse['global-state-delta'] || transactionResponse['local-state-delta']) } /** * Print to stdout the changes introduced by the transaction that generated the transactionResponse if any. * @param {Object} transactionResponse object containing the transaction response of an application call * @return {void} */ function printAppCallDelta (transactionResponse) { if (transactionResponse['global-state-delta'] !== undefined) { console.log('Global State updated:') printAppCallDeltaArray(transactionResponse['global-state-delta']) } if (transactionResponse['local-state-delta'] !== undefined) { console.log('Local State updated:') printAppCallDeltaArray(transactionResponse['local-state-delta']) } } module.exports = { timeoutPromise, getInt64Bytes, addressFromByteBuffer, printAppStateArray, printAppState, printAppLocalState, printAppGlobalState, readCreatedApps, readOptedInApps, readAppGlobalState, readAppGlobalStateByKey, readAppLocalState, readAppLocalStateByKey, uintArray8ToString, anyAppCallDelta, printAppCallDelta }