Analog incentivized testnet
Project Name - My Analog testnet
hrk3xUWrfYxinrPDju8mBPiXAioEMCfX2PVwyHjJpkC
Contract adress (адрес Smart contract) - 0x6f9da5448dc93eb1eba92d88c3518997144e949d (после Deploy)
Адрес контракта: https://sepolia.etherscan.io/address/0x6f9da5448dc93eb1eba92d88c3518997144e949d
Transaction Hash: 0x149c513914e324628d57ff0de524fbe37c61e46a8f6f2f7733a36096408b66dd
Sepolia Transaction Hash: 0xb1c0d4dc2d50c2b9065df8a0bc24290ba19a5c8ffb810f9137fceee442fbb19d
Адрес транзакции https://sepolia.etherscan.io/tx/0xb1c0d4dc2d50c2b9065df8a0bc24290ba19a5c8ffb810f9137fceee442fbb19d
Smart Contract Address - 0x69A1D6499a7B4d90cE9300A591635337C7E748Cb
https://shibuya.blockscout.com/address/0x69A1D6499a7B4d90cE9300A591635337C7E748Cb?tab=contract
Analog - протокол межсетевой связи нулевого уровня, предназначенный для взаимодействия между различными блокчейнами, привелекший более 16M$ инвестиций.
Запустили награждаемый тестнет, на который выделено 1 811 594 токенов, что является 2% от общего сапплая.
Прежде чем начать делать тестнет, если вы не подавали заявку, то
1. переходим на сайт, регистрируемся в тестнет и отмечаем что являемся девелопером, выполняем задачи, которые нас продвинут в топе, и ждем наше приглашение на тестнет (должно придти письмо на почту с таким содержанием)
2. Если у вас есть такое письмо на почте, то логинимся через эту почту на сайте.
3. Для прохождения тестнета, нам нужен кошелек от polkadot, есть целая документация про кошельки которые поддерживает analog. Я выбрал subwallet, устанавливаем его, и создаем кошелек.
4. Переходим в раздел квесты и выполняем по очереди.
- Подключаем кошелек здесь.
- Переходим сюда, и создаем API key
- Задание с созданием смарт контракта работает как то криво, у меня не считает, да и в целом не выполняется адекватно, первый раз я смог его сделать, но не засчитало, второй раз не получается, скипаем его.
- Переходим в Library, выбираем смарт контракт(я выбрал этот) и жмем create view справа от смарт контракта.
1. В появившемся окне я выбрал ALT owner слева, и по центру ввёл данные ( любые но должны быть уникально названы, и в самом верху вводим название запроса (тоже любое, но индивидуальное, потому что на следующем этапе может выдать ошибку.
2. И жмем справа снизу Test Query
5. В появившемся окне, ставим галочки ( любые ) и ставим галочку на submit отправляем нажав на complete
- Переходим в Library > Views и выбираем любой из списка, и жмем Add Funds
В открывшемся окне вводим небольшую сумму для депозита в view
Теперь переходим на страницу квестов, и проверяем что все задачи выполнились ( может занять некоторое время 1-5 мин )
На этом первая фаза тестнета для разработчиков - выполнена.
Будет еще 2 фаза, и 3 фаза, каждая будет длится ~3 месяца, более подробно ознакомится с тестнетом можно по ссылкам:
- Информация про тестнет от Coinlist
- Поискать информацию в твиттере
- Поискать информацию в дискорде
Отдельная страница с квестами на GALXE
Вторая фаза
взято тут https://mirror.xyz/0xf468f5EDCd59f2eEBffD230bEd84be8C5F4cCc65/AXWxWrcATiuVX4E8WIJeIE8BFvEFmtKQ-GH4j1i43l4
Переходим к основной части тестнета под названием DEVELOPER
8
.Для начала вам нужно установить кошелек TALISMAN авторизовать кош и выбросить сид фразу))))
9
.После активации кошелька, заходим в его SETTINGS далее >>> Networks and Tokens, далее>>>Manage networks выбираем сеть POLKADOT и добавляем сеть ANALOG TESTNET как показано на скрине, после Нажимаем Copy Adress и из списка копируем адресс кошелька ANALOG TESTNET
10.
После, чего отправляемся в дискорд проекта и в ветке faucet вставляем свой кош в формате !faucet ewbfebt3gth53hg35h3r3ef3. Ура! Вы получили тестовые токены
11
.После чего конектим кош на этом САЙТЕ!Тыкаем что хотим:
12
. Заходим в My Profile, сразу пополняем свой ак тестовыми токенами, которые мы уже получили, тыкаем на DEPOSIT и апруваем транзу в коше:
13
.Сразу выполняем задание с созданием API Keys. Тыкаем на иконку.Create new API.Вписываем никнейм и подписываем транзу
14
. **Далее отправляемся выполнять ежедневные квесты во вкладке DEVELOPERS **
Build and deploy SMART CONTRACT.
Для выполнения именно этого и других заданий в этом тестнете, нам нужен будет любимый тестовый эфир в сети SEPOLIA
Взять его можно бесплатно:
ТУТ
ТУТ
ТУТ
ТУТ
**Либо, если вы дорожите своими временем и нервами, можете купить всего за 2 доллара, практически в любой сети 1.2ETH SEPOLIA ЗДЕСЬ
**16.
После того когда на нашем коше появился тестовый ETH SEPOLIA, открываем REMIX
17
.Создаем папку и файл( сприставкой .sol) как показано на скрине
18
.После чего вставляем этот код в компилятор
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IGmpReceiver { function onGmpReceived( bytes32 id, uint128 network, bytes32 source, bytes calldata payload ) external payable returns (bytes32); } contract Counter is IGmpReceiver { address private immutable _gateway; uint256 public number; constructor(address gateway) { _gateway = gateway; } function onGmpReceived( bytes32, uint128, bytes32, bytes calldata ) external payable returns (bytes32) { require(msg.sender == _gateway, "unauthorized"); number++; return bytes32(number); } }
18
.Нажимаем компилер, выставляем версию 0.8.26 , ждем апрува и зеленой галочки и преходим к деплою
19
.В окно AdressGateway вставляем оф адресс контракта
0xB5D83c2436Ad54046d57Cd48c00D619D702F3814
После чего нажимаем Deploy и обязательно проверяем, чтобы в окне Injected Providet - было выбрано Metamask и сеть Sepolia!!!
Подписываем транзу в метамаске
20
.Далее нажимаем Debug и копируем адрес контракта!
21
.После чего переходим на этот САЙТ
22
.Вставляем адрес и и заполняем все с такими настройками как на скрине
23
.Вставляем наш код, который использовали в к ремиксе и нажимаем VERIFY & Publish
24.
Копируем наш верифнутый контракт и вставляем в окно с этим заданием, нажимаем клейм и получаем свои 15 АТP
25
.Следующие задание с отправкой сообщений, также переходим в ремикс.Для этого задания вам понадобиться сохранить в одну папку 4 этих кодов-файлов с такими названиями
// SPDX-License-Identifier: MIT // Analog's Contracts (last updated v0.1.0) (src/utils/BranchlessMath.sol) pragma solidity ^0.8.20; /** * @dev Utilities for branchless operations, useful when a constant gas cost is required. */ library BranchlessMath { /** * @dev Returns the smallest of two numbers. */ function min(uint256 x, uint256 y) internal pure returns (uint256) { return select(x < y, x, y); } /** * @dev Returns the largest of two numbers. */ function max(uint256 x, uint256 y) internal pure returns (uint256) { return select(x > y, x, y); } /** * @dev If `condition` is true returns `a`, otherwise returns `b`. */ function select(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { unchecked { // branchless select, works because: // b ^ (a ^ b) == a // b ^ 0 == b // // This is better than doing `condition ? a : b` because: // - Consumes less gas // - Constant gas cost regardless the inputs // - Reduces the final bytecode size return b ^ ((a ^ b) * toUint(condition)); } } /** * @dev If `condition` is true returns `a`, otherwise returns `b`. */ function select(bool condition, address a, address b) internal pure returns (address) { return address(uint160(select(condition, uint256(uint160(a)), uint256(uint160(b))))); } /** * @dev If `condition` is true return `value`, otherwise return zero. */ function selectIf(bool condition, uint256 value) internal pure returns (uint256) { unchecked { return value * toUint(condition); } } /** * @dev Unsigned saturating addition, bounds to UINT256 MAX instead of overflowing. * equivalent to: * uint256 r = x + y; * return r >= x ? r : UINT256_MAX; */ function saturatingAdd(uint256 x, uint256 y) internal pure returns (uint256) { unchecked { x = x + y; y = 0 - toUint(x < y); return x | y; } } /** * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing. * equivalent to: x > y ? x - y : 0 */ function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { // equivalent to: a > b ? a - b : 0 return (a - b) * toUint(a > b); } } /** * @dev Unsigned saturating multiplication, bounds to `2 ** 256 - 1` instead of overflowing. */ function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { uint256 c = a * b; bool success; assembly { // Only true when the multiplication doesn't overflow // (c / a == b) || (a == 0) success := or(eq(div(c, a), b), iszero(a)) } return c | (toUint(success) - 1); } } /** * @dev Unsigned saturating division, bounds to UINT256 MAX instead of overflowing. */ function saturatingDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { assembly { r := div(x, y) } } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds towards infinity instead * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { // The following calculation ensures accurate ceiling division without overflow. // Since a is non-zero, (a - 1) / b will not overflow. // The largest possible result occurs when (a - 1) / b is type(uint256).max, // but the largest value we can obtain is type(uint256).max - 1, which happens // when a = type(uint256).max and b = 1. return selectIf(a > 0, ((a - 1) / b + 1)); } } /** * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. */ function toUint(bool b) internal pure returns (uint256 u) { /// @solidity memory-safe-assembly assembly { u := iszero(iszero(b)) } } /** * @dev Cast an address to uint256 */ function toUint(address addr) internal pure returns (uint256) { return uint256(uint160(addr)) } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IGmpReceiver { function onGmpReceived(bytes32 id, uint128 network, bytes32 source, bytes calldata payload) external payable returns (bytes32); } contract Counter is IGmpReceiver { address private immutable _gateway; uint256 public number; // address 0x000000007f56768de3133034fa730a909003a165 constructor(address gateway) { _gateway = gateway; } function onGmpReceived(bytes32, uint128, bytes32, bytes calldata) external payable returns (bytes32) { require(msg.sender == _gateway, "unauthorized"); number++; return bytes32(number); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {GmpSender} from "./Primitives.sol"; /** * @dev Required interface of an Gateway compliant contract */ /* At Address - 0x000000007f56768de3133034fa730a909003a165 submitMessage: gasLimit 30000 data 0x01 sepolia address 0xF871c929bE8Cd8382148C69053cE5ED1a9593EA7 and net 7 shibuya address 0xB5D83c2436Ad54046d57Cd48c00D619D702F3814 and net 5 */ interface IGateway { event GmpCreated( bytes32 indexed id, bytes32 indexed source, address indexed destinationAddress, uint16 destinationNetwork, uint256 executionGasLimit, uint256 salt, bytes data ); function networkId() external view returns (uint16); function deposit(GmpSender sender, uint16 sourceNetwork) external payable; function depositOf(GmpSender sender, uint16 sourceNetwork) external view returns (uint256); function submitMessage( address destinationAddress, uint16 destinationNetwork, uint256 executionGasLimit, bytes calldata data ) external payable returns (bytes32); }
// SPDX-License-Identifier: MIT // Analog's Contracts (last updated v0.1.0) (src/Primitives.sol) pragma solidity >=0.8.0; import {BranchlessMath} from "./BranchlessMath.sol"; /** * @dev GmpSender is the sender of a GMP message */ type GmpSender is bytes32; /** * @dev Tss public key * @param yParity public key y-coord parity, the contract converts it to 27/28 * @param xCoord affine x-coordinate */ struct TssKey { uint8 yParity; uint256 xCoord; } /** * @dev Schnorr signature. * OBS: what is actually signed is: keccak256(abi.encodePacked(R, parity, px, nonce, message)) * Where `parity` is the public key y coordinate stored in the contract, and `R` is computed from `e` and `s` parameters. * @param xCoord public key x coordinates, y-parity is stored in the contract * @param e Schnorr signature e component * @param s Schnorr signature s component */ struct Signature { uint256 xCoord; uint256 e; uint256 s; } /** * @dev GMP payload, this is what the timechain creates as task payload * @param source Pubkey/Address of who send the GMP message * @param srcNetwork Source chain identifier (for ethereum networks it is the EIP-155 chain id) * @param dest Destination/Recipient contract address * @param destNetwork Destination chain identifier (it's the EIP-155 chain_id for ethereum networks) * @param gasLimit gas limit of the GMP call * @param salt Message salt, useful for sending two messages with same content * @param data message data with no specified format */ struct GmpMessage { GmpSender source; uint16 srcNetwork; address dest; uint16 destNetwork; uint256 gasLimit; uint256 salt; bytes data; } /** * @dev Message payload used to revoke or/and register new shards * @param revoke Shard's keys to revoke * @param register Shard's keys to register */ struct UpdateKeysMessage { TssKey[] revoke; TssKey[] register; } /** * @dev Message payload used to revoke or/and register new shards * @param revoke Shard's keys to revoke * @param register Shard's keys to register */ struct Network { uint16 id; address gateway; } /** * @dev Status of a GMP message */ enum GmpStatus { NOT_FOUND, SUCCESS, REVERT, INSUFFICIENT_FUNDS, PENDING } /** * @dev EIP-712 utility functions for primitives */ library PrimitiveUtils { /** * @dev GMP message EIP-712 Type Hash. * Declared as raw value to enable it to be used in inline assembly * keccak256("GmpMessage(bytes32 source,uint16 srcNetwork,address dest,uint16 destNetwork,uint256 gasLimit,uint256 salt,bytes data)") */ bytes32 internal constant GMP_MESSAGE_TYPE_HASH = 0xeb1e0a6b8c4db87ab3beb15e5ae24e7c880703e1b9ee466077096eaeba83623b; function toAddress(GmpSender sender) internal pure returns (address) { return address(uint160(uint256(GmpSender.unwrap(sender)))); } function toSender(address addr, bool isContract) internal pure returns (GmpSender) { uint256 sender = BranchlessMath.toUint(isContract) << 160 | uint256(uint160(addr)); return GmpSender.wrap(bytes32(sender)); } // computes the hash of an array of tss keys function eip712hash(TssKey memory tssKey) internal pure returns (bytes32) { return keccak256(abi.encode(keccak256("TssKey(uint8 yParity,uint256 xCoord)"), tssKey.yParity, tssKey.xCoord)); } // computes the hash of an array of tss keys function eip712hash(TssKey[] memory tssKeys) internal pure returns (bytes32) { bytes memory keysHashed = new bytes(tssKeys.length * 32); uint256 ptr; assembly { ptr := keysHashed } for (uint256 i = 0; i < tssKeys.length; i++) { bytes32 hash = eip712hash(tssKeys[i]); assembly { ptr := add(ptr, 32) mstore(ptr, hash) } } return keccak256(keysHashed); } // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer function eip712hash(UpdateKeysMessage memory message) internal pure returns (bytes32) { return keccak256( abi.encode( keccak256("UpdateKeysMessage(TssKey[] revoke,TssKey[] register)TssKey(uint8 yParity,uint256 xCoord)"), eip712hash(message.revoke), eip712hash(message.register) ) ); } function eip712TypedHash(UpdateKeysMessage memory message, bytes32 domainSeparator) internal pure returns (bytes32) { return _computeTypedHash(domainSeparator, eip712hash(message)); } function eip712hash(GmpMessage memory message) internal pure returns (bytes32 id) { bytes memory data = message.data; /// @solidity memory-safe-assembly assembly { // keccak256(message.data) id := keccak256(add(data, 32), mload(data)) // now compute the GmpMessage Type Hash without memory copying let offset := sub(message, 32) let backup := mload(offset) { mstore(offset, GMP_MESSAGE_TYPE_HASH) { let offset2 := add(offset, 0xe0) let backup2 := mload(offset2) mstore(offset2, id) id := keccak256(offset, 0x100) mstore(offset2, backup2) } } mstore(offset, backup) } } function encodeCallback(GmpMessage calldata message, bytes32 domainSeparator) internal pure returns (bytes32 messageHash, bytes memory r) { bytes calldata data = message.data; /// @solidity memory-safe-assembly assembly { r := mload(0x40) // GmpMessage Type Hash mstore(add(r, 0x0004), GMP_MESSAGE_TYPE_HASH) mstore(add(r, 0x0024), calldataload(add(message, 0x00))) // message.source mstore(add(r, 0x0044), calldataload(add(message, 0x20))) // message.srcNetwork mstore(add(r, 0x0064), calldataload(add(message, 0x40))) // message.dest mstore(add(r, 0x0084), calldataload(add(message, 0x60))) // message.destNetwork mstore(add(r, 0x00a4), calldataload(add(message, 0x80))) // message.gasLimit mstore(add(r, 0x00c4), calldataload(add(message, 0xa0))) // message.salt // Copy message.data to memory let size := data.length mstore(add(r, 0x0104), size) // message.data length calldatacopy(add(r, 0x0124), data.offset, size) // message.data // Computed GMP Typed Hash messageHash := keccak256(add(r, 0x0124), size) // keccak(message.data) mstore(add(r, 0x00e4), messageHash) messageHash := keccak256(add(r, 0x04), 0x0100) // GMP eip712 hash mstore(0, 0x1901) mstore(0x20, domainSeparator) mstore(0x40, messageHash) // this will be restored at the end of this function messageHash := keccak256(0x1e, 0x42) // GMP Typed Hash // onGmpReceived size := and(add(size, 31), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) size := add(size, 0xa4) mstore(add(r, 0x0064), 0x01900937) // selector mstore(add(r, 0x0060), size) // length mstore(add(r, 0x0084), messageHash) // GMP Typed Hash mstore(add(r, 0x00a4), calldataload(add(message, 0x20))) // msg.network mstore(add(r, 0x00c4), calldataload(add(message, 0x00))) // msg.source mstore(add(r, 0x00e4), 0x80) // msg.data offset size := and(add(size, 31), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0) size := add(size, 0x60) mstore(0x40, add(add(r, size), 0x40)) r := add(r, 0x60) } } function eip712TypedHash(GmpMessage memory message, bytes32 domainSeparator) internal pure returns (bytes32 messageHash) { messageHash = eip712hash(message); messageHash = _computeTypedHash(domainSeparator, messageHash); } function _computeTypedHash(bytes32 domainSeparator, bytes32 messageHash) private pure returns (bytes32 r) { /// @solidity memory-safe-assembly assembly { mstore(0, 0x1901000000000000000000000000000000000000000000000000000000000000) mstore(0x02, domainSeparator) mstore(0x22, messageHash) r := keccak256(0, 0x42) mstore(0x22, 0) } } }
26
.**Сохраняем в папку и нажимаем в ремиксе Open your file from your file system. Выбираем все файлы и добавляем в ремикс **
27
.После чего выбираем IGateway.sol, нажимаем compile, Injected Provider-Metamask, сеть Sepolia
28
.Ждем апрува и переходим к отправке сообщения. В окно At address добавляем этот адресс 0x000000007f56768de3133034fa730a909003a165, как показано на скрине
29
После чего появляется igataway окно , где нужно выбрать SUBMIT MESSAGE и заполнить следующие данные:
Destination address: 0xF871c929bE8Cd8382148C69053cE5ED1a9593EA7
30
.После чего подписываем транзакцию в мм и копируем хэш как на скрине
31
.И вставляем его в это задание и получаем 20 ATP*( есть нюанс, транзакция идет от пол часа, поэтому нужно вставить и нажать клейм спустя какое то время)*
32
.Далее нам нужно выполнить те же задания с только в сети SHIBULIA.Для этого нам нужны тестовые токены в этой сети. Отправляемся СЮДА, подключаем кошелек, добавляем сеть SHIBULIA , нажимаем на капельку и получаем тестовые токены
33
.После чего отправляемся на Remix и проделываем в се тоже самое, что и в предыдущий раз с первым деплоем, только выбрав сеть SHIBULIAСоздаем файл, вставляем этот код
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGmpReceiver {
function onGmpReceived(bytes32 id, uint128 network, bytes32 source, bytes calldata payload)
external
payable
returns (bytes32);
}
contract Counter is IGmpReceiver {
address private immutable _gateway;
uint256 public number;
// example gw 0x7702eD777B5d6259483baAD0FE8b9083eF937E2A constructor(address gateway) { _gateway = gateway; function onGmpReceived(bytes32, uint128, bytes32, bytes calldata) external payable override returns (bytes32) { require(msg.sender == _gateway, "unauthorized"); number++; return bytes32(number); }}mber); }
34`.Нажимаем Compile, ждем верифа, переходим к деплою и в окно Att adress вставляем уже этот адресс
0x7702eD777B5d6259483baAD0FE8b9083eF937E2A
Нажимаем деплой, подписываем транзакцию и копируем адресс контракта.
35
.После чего отправляемся СЮДА, вставляем адресс контракта, выбираем все те же настройки что и при предыдущем верифе, вставляем этот же код, что и в ремиксе и верифнутый контракт вставляем в окно с заданием на сайте и получаем 15 ATP!
36
.Задание с отправкой сообщения в сети Shibulia приостановили:( и по ответам модереров в дисе, скорее всего уже не вернут. Поэтому нам осталось выполнить ежедневные задания на сайте ANALOG WATCH
**
37.Для выполнения выполнения задание LIST SMART CONTRACT нам также необходимо задеплоить ещё один смарт котракт. Также отправляемся на REMIX
Создаем файл с приставкой .sol как и в прошлые разы. Юзать также можно этот код
**
`
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGmpReceiver {
function onGmpReceived(bytes32 id, uint128 network, bytes32 source, bytes calldata payload)
external
payable
returns (bytes32);
}
contract Counter is IGmpReceiver {
address private immutable _gateway;
uint256 public number;
// example gw 0x7702eD777B5d6259483baAD0FE8b9083eF937E2A constructor(address gateway) { _gateway = gateway; } function onGmpReceived(bytes32, uint128, bytes32, bytes calldata) external payable returns (bytes32) { require(msg.sender == _gateway, "unauthorized"); number++; return bytes32(number); }
**Также выбираем сеть sepolia, в окно Adress вставляем этот код
0x7702eD777B5d6259483baAD0FE8b9083eF937E2A
Нажимаем делой, подписываем транзу и копируем адресс контракта **
38.Далее идем на САЙТ, нажимаем Smart Contract
Вставляем этот адрес, пишем символ, придумываем рандомное описание и выбираем теги, и нажимаем add function
39
.Далее нужно вставить ABI, находится он в ремиксе, вот здесь ! Копируем его и вставляем !
40.Выбираем эту функцию и тыкаем LIST
41
.Сюдаааа, задание с контрактом мы выолняли! Выполняй это упражнение 1 раз в день и получай 5 АТP.Юзать один и тот же код можно! Главное каждый раз создавать новый смарт котракт
42
.Далее нам нужно выолнить задание Build and deploy a View!Перходим сюда и из 163000 тысяч контрактов, находим те, где больше 2 функций, вписаваем рандом как на скрине и нажимаем Test query, если выдает ошибку, gjghj,eqnt новый контракт! Почему то деплоются только те, что с больше 2 функциями
43
.Нажимаем деплой, также вписываем теги и описание и нажимаем некст
** Нажимаем Go to view PAge и нажмаем Add Funds.
Донатим немного тестовых токенов.**
44
.Вот мы и выполнили тестовое дейлки на вотч гейм. Выполняя 3 задания в день, фармится 22ATP
Есть выжившие? Кто дошел до конца - настоящий красавец, ведь сложность отработки проекта уменьшает конкуренцию, что увеличивает шансы на больший профит!До конца тестнета остается около 3 недель, а информацию о точной дате TGE и запуске майнета команда должны сообщить в ближайшее время! Если ещё не отрабатывал проект, самое время запрыгнуть в последний вагон. LFG!!!
Также настоятельно рекомендую выполнить на фулл гелакси компанию и иметь транзы и мин баланс в ефир сетке. Проекты, которые запускают тестнеты- часто за это бреют. Всем удачи!!!