First commit

This commit is contained in:
2025-05-13 19:41:33 +02:00
commit 1ec1154796
21 changed files with 7373 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
src/js/metacoin-config.js
node_modules
build
.env
tmp
_old
.venv
*.mnemonic
*_wallet.json
*.log
_contracts_old

24
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.fontFamily": "'Cascadia Code PL', Consolas, 'Courier New', monospace",
"editor.fontLigatures": true,
"editor.guides.bracketPairs": true,
"editor.linkedEditing": true,
"editor.minimap.enabled": false,
"editor.tabSize": 2,
"editor.useTabStops": true,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"files.trimTrailingWhitespace": true,
"github.copilot.enable": {
"*": true,
"markdown": false,
"plaintext": false,
"scminput": false
},
"prettier.semi": false,
"prettier.singleQuote": true,
"prettier.trailingComma": "none",
"terminal.integrated.defaultProfile.linux": "JavaScript Debug Terminal",
"terminal.integrated.defaultProfile.windows": "JavaScript Debug Terminal"
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License Copyright (c) 2025 Toni Ramiro <sargatxet@gmail.com>
Permission is hereby granted, free of
charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice
(including the next paragraph) shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Sarga TRX/USD value getter

19
contracts/Migrations.sol Normal file
View File

@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract Migrations {
address public owner = msg.sender;
uint public last_completed_migration;
modifier restricted() {
require(
msg.sender == owner,
"This function is restricted to the contract's owner"
);
_;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract SargaTrxUsdPrice {
AggregatorV3Interface internal _priceFeed;
/**
* Network: Tron
* Aggregator: TRX/USD
* Address: TC6o8AakUg4Xz9nHY9qXpJNsgF7CQkwBqF
*/
constructor(address priceFeedAddress) {
_priceFeed = AggregatorV3Interface(priceFeedAddress);
}
/**
* Returns the latest price
*/
function getLatestPrice() public view returns (int) {
(
,
/* uint80 roundID */ int price /* uint startedAt */ /* uint timeStamp */ /* uint80 answeredInRound */,
,
,
) = _priceFeed.latestRoundData();
return price;
}
}

8
docker-compose.yml Normal file
View File

@@ -0,0 +1,8 @@
services:
tron:
image: tronbox/tre
restart: unless-stopped
ports:
- 9090:9090
# volumes:
# - ./accounts-data:/config # node will load these keys

13
lib/logger.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict'
// Log
const logMessage = (source, msg) => {
console.log(new Date(), `[${source}]`, msg)
}
// Error
const errorMessage = (source, msg) => {
console.error(new Date(), `[${source}]`, msg)
}
module.exports = { logMessage, errorMessage }

84
lib/tron.js Normal file
View File

@@ -0,0 +1,84 @@
'use strict'
// Environment
require('dotenv').config()
// Libraries
const path = require('path')
const fs = require('fs')
const { decryptString } = require('./utils')
// Connect to Tron network
const TronWeb = require('tronweb')
// Set Tron connection
const API_URL =
process.env.DEBUG === '1'
? process.env.TRON_NILE_API
: process.env.DEBUG === '2'
? process.env.TRON_LOCAL_API
: process.env.TRON_MAINNET_API
const HttpProvider = TronWeb.providers.HttpProvider
const fullNode = new HttpProvider(API_URL)
const solidityNode = new HttpProvider(API_URL)
const eventServer = API_URL
const tronWeb = new TronWeb(fullNode, solidityNode, eventServer)
const feeLimit = parseInt(process.env.FEE_LIMIT)
// Set contract data
const contractPath = path.join(
__dirname,
`../build/contracts/${process.env.CONTRACT_NAME}.json`
)
const contractData = JSON.parse(fs.readFileSync(contractPath))
const contract = tronWeb.contract(
contractData.abi,
process.env.CONTRACT_ADDRESS
)
// TRX to SUN conversion
function trxToSun(trx) {
const sun = +(+trx * 1e6).toFixed(0)
return sun
}
// SUN to TRX conversion
function sunToTrx(sun) {
const trx = +(+sun / 1e6).toFixed(6)
return trx
}
// Address to base58 conversion
function addressToBase58(address) {
return tronWeb.address.fromHex(address)
}
// Set default private key
function setDefaultPrivateKey(pk) {
// Decrypt private key
const decPK =
pk ||
decryptString(process.env.PRIVATE_KEY_MAINNET, process.env.SEED_PASSWORD)
// Set private key in TronWeb
tronWeb.setPrivateKey(decPK.substring(2))
return decPK
}
// Get TRX balance
const getTrxBalance = async (address) => {
const balance = sunToTrx(await tronWeb.trx.getBalance(address))
return balance
}
module.exports = {
tronWeb,
setDefaultPrivateKey,
feeLimit,
contractData,
contract,
trxToSun,
sunToTrx,
addressToBase58,
getTrxBalance
}

73
lib/utils.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict'
// Libraries
const { customAlphabet } = require('nanoid')
const StringCrypto = require('string-crypto')
const path = require('path')
// NanoId with custom alphabet
const newId = (len) => {
const nanoid = customAlphabet(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
len || 21
)
return nanoid()
}
// Remove all _id
const removeFieldByName = (o, fieldName) => {
delete o[fieldName]
for (let v of Object.values(o))
if (v instanceof Object) removeFieldByName(v, fieldName)
}
// Clean object from unnecesary fields
const fieldsToRemove = ['_id']
const cleanObject = (o) => {
const obj = JSON.parse(JSON.stringify(o))
fieldsToRemove.forEach((f) => {
removeFieldByName(obj, f)
})
return obj
}
// Securize sensible data
const cryptoOptions = {
salt: process.env.CRYPTO_SALT,
iterations: parseInt(process.env.CRYPTO_ITERATIONS),
digest: process.env.CRYPTO_DIGEST // one of: 'blake2b512' | 'blake2s256' | 'md4' | 'md5' | 'md5-sha1' | 'mdc2' | 'ripemd160' | 'sha1' | 'sha224' | 'sha256' | 'sha3-224' | 'sha3-256' | 'sha3-384' | 'sha3-512' | 'sha384' | 'sha512' | 'sha512-224' | 'sha512-256' | 'sm3' | 'whirlpool';
}
const stringCypto = new StringCrypto(cryptoOptions)
const encryptString = (value, password) => {
return stringCypto.encryptString(value, password)
}
const decryptString = (value, password) => {
return stringCypto.decryptString(value, password)
}
// Split array into pieces
const chunk = (arr, size) =>
arr.reduce(
(acc, _, i) => (i % size ? acc : [...acc, arr.slice(i, i + size)]),
[]
)
// Sleep
const sleep = (segundos) => {
return new Promise((resolve) => setTimeout(resolve, segundos * 1000))
}
// Get file name
const getFileName = (f) => path.basename(f, '.js')
module.exports = {
newId,
removeFieldByName,
cleanObject,
encryptString,
decryptString,
chunk,
sleep,
getFileName
}

View File

@@ -0,0 +1,7 @@
require('dotenv').config()
var Migrations = artifacts.require('./Migrations.sol')
module.exports = function (deployer) {
deployer.deploy(Migrations)
}

View File

@@ -0,0 +1,7 @@
require('dotenv').config()
var MyContract = artifacts.require('./SargaTrxUsdPrice.sol')
module.exports = function (deployer) {
deployer.deploy(MyContract)
}

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "sarga_tron_usd_price",
"version": "0.1.0",
"description": "Contract to get TRX/USD value",
"main": "index.js",
"author": "Toni Ramiro <sargatxet@gmail.com>",
"license": "MIT",
"private": true,
"dependencies": {
"@chainlink/contracts": "1.4.0",
"@noble/secp256k1": "1.7.1",
"dotenv": "16.4.7",
"solc": "0.8.20",
"string-crypto": "2.0.2",
"tronbox": "4.1.1",
"tronweb": "5.3.0"
},
"devDependencies": {
"@openzeppelin/test-helpers": "0.5.16",
"chai": "5.2.0"
}
}

1
sample-env Normal file
View File

@@ -0,0 +1 @@
export PRIVATE_KEY_SHASTA=0000000000000000000000000000000000000000000000000000000000000001

View File

@@ -0,0 +1,93 @@
require('dotenv').config()
// Mainnet
process.env.DEBUG = '0'
const fs = require('fs')
const { logMessage, errorMessage } = require('../lib/logger')
const {
tronWeb,
setDefaultPrivateKey,
contractData,
feeLimit,
sunToTrx,
addressToBase58
} = require('../lib/tron')
const { sleep } = require('../lib/utils')
// Binary data of the contract and ABI
const bytecode = contractData.bytecode
const abi = contractData.abi
logMessage('1_deploy_on_mainnet', 'Bytecode and ABI loaded')
// Get TRX balance
const getTrxBalance = async (address) => {
const balance = sunToTrx(await tronWeb.trx.getBalance(address))
logMessage('1_deploy_on_mainnet', `TRX balance: ${balance} TRX`)
return balance
}
async function deployContract() {
try {
setDefaultPrivateKey()
logMessage('1_deploy_on_mainnet', 'Deploying contract...')
// Get TRX balance
const address = tronWeb.defaultAddress.base58
const balance = await getTrxBalance(address)
if (balance < 200) {
throw new Error('Insufficient TRX balance to deploy the contract')
}
// Sign transaction
const unsignedTxn = await tronWeb.transactionBuilder.createSmartContract({
abi,
bytecode,
feeLimit,
callValue: 0, // TRX sent to the contract
parameters: [process.env.CHAINLINK_USD_PRICE_FEED]
})
const signedTxn = await tronWeb.trx.sign(unsignedTxn)
logMessage('1_deploy_on_mainnet', 'Transaction signed')
// Broadcast transaction
const tx = await tronWeb.trx.sendRawTransaction(signedTxn)
logMessage('1_deploy_on_mainnet', `Transaction ID: ${tx.txid}`)
logMessage('1_deploy_on_mainnet', 'Contract deployed')
// Get contract address
let contractAddress = {}
do {
await sleep(5) // Wait 5 seconds
contractAddress = await tronWeb.trx.getTransactionInfo(tx.txid)
} while (!contractAddress.contract_address)
logMessage(
'1_deploy_on_mainnet',
`Contract address: ${contractAddress.contract_address}`
)
// Add/Change into .env
let envContent = fs.readFileSync('.env', 'utf8')
if (/CONTRACT_ADDRESS=/.test(envContent)) {
envContent = envContent.replace(
/CONTRACT_ADDRESS=.*/,
`CONTRACT_ADDRESS=${addressToBase58(contractAddress.contract_address)}`
)
} else {
envContent += `\n\n# Contract address\nCONTRACT_ADDRESS=${addressToBase58(
contractAddress.contract_address
)}`
}
fs.writeFileSync('.env', envContent)
process.exit(0)
} catch (error) {
errorMessage(
'1_deploy_on_mainnet',
`ERROR: ${error.message || JSON.stringify(error)}`
)
}
}
deployContract()

1
test/.git-folder-keeper Normal file
View File

@@ -0,0 +1 @@
This is a placeholder file to ensure the parent directory in the git repository. Feel free to remove.

View File

@@ -0,0 +1,41 @@
// test/SargaTrxUsdPrice.test.js
require('dotenv').config()
const TronWeb = require('tronweb')
const HttpProvider = TronWeb.providers.HttpProvider
const API_URL = process.env.TRON_API
const fullNode = new HttpProvider(API_URL)
const solidityNode = new HttpProvider(API_URL)
const eventServer = API_URL
const tronWeb = new TronWeb(fullNode, solidityNode, eventServer)
let assert
const SargaTrxUsdPrice = artifacts.require('SargaTrxUsdPrice')
contract('SargaTrxUsdPrice', () => {
it('get TRX/USD value', async () => {
// carga Chai como ESM y extrae assert
const chaiModule = await import('chai')
assert = chaiModule.assert
// 1) Load already deployed contract
const distributor = await SargaTrxUsdPrice.at(process.env.CONTRACT_ADDRESS)
// 2) Call the getTrxUsdPrice function
const trxUsdPrice = await distributor.getLatestPrice().call()
console.log(
'TRX/USD price:',
parseFloat(trxUsdPrice.toString()) / 10 ** 8,
'USD'
)
// 3) Assert the value is greater than 0
assert.isAbove(
parseFloat(trxUsdPrice),
0,
'TRX/USD price should be greater than 0'
)
})
})

3
tronbox-config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
};

44
tronbox-evm-config.js Normal file
View File

@@ -0,0 +1,44 @@
module.exports = {
networks: {
bttc: {
// Don't put your private key here:
privateKey: process.env.PRIVATE_KEY_BTTC,
/*
Create a .env file (it must be gitignored) containing something like
export PRIVATE_KEY_BTTC=4E7FEC...656243
Then, run the migration with:
source .env && tronbox migrate --network bttc --evm
*/
fullHost: 'https://rpc.bt.io',
// gas: 8500000, // Gas sent with each transaction
// gasPrice: '500000000000000', // 500,000 gwei (in wei)
network_id: '1'
},
donau: {
privateKey: process.env.PRIVATE_KEY_DONAU,
fullHost: 'https://pre-rpc.bt.io',
network_id: '2'
},
development: {
privateKey: process.env.PRIVATE_KEY_DEV,
fullHost: 'http://127.0.0.1:8545',
network_id: '9'
}
},
compilers: {
solc: {
version: '0.8.7',
settings: {
// optimizer: {
// enabled: true,
// runs: 200
// },
// evmVersion: 'istanbul',
// viaIR: true,
}
}
}
};

66
tronbox.js Normal file
View File

@@ -0,0 +1,66 @@
const port = process.env.HOST_PORT || 9090
module.exports = {
networks: {
mainnet: {
// Don't put your private key here:
privateKey: process.env.PRIVATE_KEY_MAINNET,
/*
Create a .env file (it must be gitignored) containing something like
export PRIVATE_KEY_MAINNET=4E7FEC...656243
Then, run the migration with:
source .env && tronbox migrate --network mainnet
*/
userFeePercentage: 100,
feeLimit: 1000 * 1e6,
fullHost: 'https://api.trongrid.io',
network_id: '1'
},
shasta: {
privateKey: process.env.PRIVATE_KEY_SHASTA,
userFeePercentage: 50,
feeLimit: 1000 * 1e6,
fullHost: 'https://api.shasta.trongrid.io',
network_id: '2'
},
nile: {
privateKey: process.env.PRIVATE_KEY_NILE,
userFeePercentage: 100,
feeLimit: 1000 * 1e6,
fullHost: 'https://nile.trongrid.io',
network_id: '3'
},
development: {
// For tronbox/tre docker image
privateKey: '0000000000000000000000000000000000000000000000000000000000000001',
userFeePercentage: 0,
feeLimit: 1000 * 1e6,
fullHost: 'http://127.0.0.1:' + port,
network_id: '9'
},
compilers: {
solc: {
version: "0.8.20", // Specify a Solidity version >= 0.8.20
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
}
},
// solc compiler optimize
solc: {
optimizer: {
enabled: true,
runs: 200
},
evmVersion: 'istanbul',
viaIR: true,
}
}

6802
yarn.lock Normal file

File diff suppressed because it is too large Load Diff