Solidity calls
You've been warned.
You can define a Solidity contract and interfaces directly in your Bloomr scripts using the solidity tag, which will automatically be typed by Typescript, and will be callable directly from JS.
Call a non-deployed utility contract​
You can use this to write Solidity utilities which may fetch complicated information directly on chain, in the context of the current block being matched.
This contract is dynamically compiled by Bloomr, and will be usable as if you had deployed it, on all blockchains.
For instance, the below example tracks all ERC20 transfers, of all tokens, for all blockchains, and fetches data about them via a Solidity helper :
const {
IERC20: { Transfer },
Main: { getTokenDetails },
} = solidity<GetTokenData>`
pragma solidity ^0.8.15;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
function decimals() external view returns (uint256);
function name() external view returns (string memory);
}
contract Main {
function getTokenDetails(IERC20 token) public view returns (uint256 decimals, string memory name) {
decimals = token.decimals();
name = token.name();
}
}
`
// just a cache to avoid re-fetching tokens we've already encountered
type R = GetTokenData.Erc20.getTokenDetails.Result;
const cache = new Map<HexString, R>();
function getTokenDataCached(address: HexString): R {
let cached = cache.get(address);
if (cached) {
return cached;
}
cached = getTokenDetails(address);
cache.set(address, cached);
return cached;
}
onTx(tx => {
// decode all transfer events, and emit a message !
for (const log of tx.logs(Transfer)) {
const {name, decimals} = getTokenDataCached(log.emittedBy);
emit({
network: tx.network,
tx: tx.hash,
name,
decimals,
amtNum: (Number(log.value) / (10**Number(decimals))),
...log,
})
}
})
Which will emit an event for each transfer on the blockchain, which will include the token's name and the corresponding numeric human-readable value that has been transfered 🎉
For instance, here is one of the messages that it has emitted:
{
"network": "polygon",
"name": "(PoS) Tether USD",
"decimals": "6",
"amtNum": 0.074727,
"from": "0x69460570c93f9de5e2edbc3052bf10125f0ca22d",
"to": "0x207a8eb7216fa4a5e5bc4db6fe0f041604c8cd9b",
"value": "74727",
"emittedBy": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
"tx": "0xa91e3e77b10920c0330665ad8f579ef62fde0abb795b22f4d0eabb8a3b5014ef"
}
This example script emits A LOT of messages when ran without filter.
We would suggest not to leave it running on your account in the background to avoid unnecessary costs !
Call an existing contract, via an interface​
If you need to call an existing contract directly, declare its interface using the abi tag.
For instance, this script tracks all ERC20 transfers, and sends an event each time a balance changes, along with the new updated balances for the parties involved:
const { IERC20 } = solidity<Erc20>`
pragma solidity ^0.8.15;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
function balanceOf(address account) external view returns (uint256);
}
`;
onTx(tx => {
for (const log of tx.logs(IERC20.Transfer)) {
// just give an address to the interface so it becomes a contract.
const token = IERC20.at(log.emittedBy);
// note: here, we're performing 2 solidity calls. This could be refactored into one using a contract,
// but this is the for the example's sake.
const toNewBalance = token.balanceOf(log.to);
const fromNewBalance = token.balanceOf(log.from);
emit({
network: tx.network,
tx: tx.hash,
toNewBalance,
fromNewBaalance,
...log,
})
}
})
You can, but you are not forced to declare events inside interfaces using the abi tag.
Perform a call as of a previous block​
You can choose to call a Solidity function on the state before the previous block.
const { IERC20 } = solidity<Erc20>`
pragma solidity ^0.8.15;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
function decimals() external view returns (uint256);
function name() external view returns (string memory);
}
`
onTx(tx => {
for (const log of tx.logs(IERC20.Transfer)) {
const token = IERC20.at(log.emittedBy);
// get the token's balance of receiver BEFORE this transfer.
const toOldBalance = token.balanceOf.onPreviousBlock(log.to);
emit({
network: tx.network,
tx: tx.hash,
toOldBalance,
...log,
})
}
})
Solidity imports​
You can choose to copy/paste interfaces (or parts of interfaces) you want to use directly in your script, but you can also import .sol files directly from URLs.
For instance, this will import the interface of an UniswapV2 liquidity pool.
const myScript = solidity<MyScript>`
pragma solidity ^0.8.15;
import "https://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/interfaces/IUniswapV2Pair.sol";
// use the interface as you wish
`
Prefer using Github commit-scoped links (not the "main" branch) to ensure that the underlying file will never change.