Ir para o conteúdo

Lançando feeds de preços com Phala

Introdução

Phala Network é uma rede de computação off-chain apoiada por Secure Enclaves que permite a desenvolvedores criar contratos inteligentes potentes conectados a componentes off-chain, chamados Phat Contracts. Os Phat Contracts foram projetados para oferecer funcionalidades que superam as limitações de contratos tradicionais (armazenamento, custo, computação), mantendo-se trustless, verificáveis e sem permissão. Para saber mais sobre a arquitetura da Phala, consulte a documentação.

A Phala não é, por si só, uma rede de oráculos; ela habilita várias capacidades de computação off-chain, incluindo uma rede descentralizada de oráculos. A Phala também oferece o Phala Bricks, um conjunto de ferramentas que facilita lançar rapidamente esse tipo de funcionalidade sem precisar construir tudo do zero.

Este tutorial mostra um demo de como interagir com feeds de preço habilitados por Phat Contracts na rede EVM de demonstração do Tanssi. Em seguida, você verá como implantar feeds de preço na sua rede EVM com tecnologia Tanssi. Para produção, é altamente recomendável contatar a equipe Phala para auxiliar no lançamento e garantir a integridade do processo.

Se você já usa outro provedor de oráculos, a Phala serve como camada de execução confidencial para trazer esses dados para sua rede Tanssi. É possível adaptar o fluxo descrito aqui para outros feeds ou APIs, mantendo a mesma interface de consumo no contrato EVM.

Além disso, por usar enclaves seguros, a Phala reduz a superfície de ataque ao processar dados sensíveis ou agregados de múltiplas fontes, reforçando a confiança no resultado final consumido pelos dApps.

Como a Phala habilita feeds de preço

A Phala espelha os Chainlink Price Feeds do Ethereum Mainnet. Esses feeds são amplamente adotados e sua coleta/agragação é feita por vários operadores de nó independentes, evitando dependência de uma única fonte de verdade e reduzindo risco de manipulação.

O componente central do desenho do sistema é o Secure Enclave, que processa as mensagens recebidas da blockchain Phala (fila de mensagens criptografada) e garante execução fiel mesmo com trabalhadores maliciosos. A blockchain Phala solicita a atualização do feed; os workers off-chain buscam os preços no Ethereum Mainnet e devolvem para a blockchain Phala.

Além de replicar oráculos existentes, é possível criar novos oráculos buscando dados off-chain via Phat Contracts. No exemplo de Phat-EVM Oracle, os preços vêm da API do CoinGecko e podem ser enviados continuamente (push) ou solicitados pelo contrato EVM (pull).

Em resumo: a Phala funciona como uma ponte segura entre dados externos e sua rede EVM, permitindo reutilizar feeds consolidados da Chainlink ou construir integrações sob medida usando Phat Contracts.

Buscar dados de preço

Há vários feeds disponíveis na rede EVM demo. Os feeds habilitados por Phat Contracts usam a mesma interface dos feeds Chainlink. Cada feed fica em um contrato e pode ser consultado pela interface agregadora:

Você também pode reutilizar a mesma interface para feeds personalizados que a sua equipe decidir publicar, mantendo uma API consistente para contratos e frontends.

AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
    /**
     * Returns the decimals to offset on the getLatestPrice call
     */
    function decimals() external view returns (uint8);

    /**
     * Returns the description of the underlying price feed aggregator
     */
    function description() external view returns (string memory);

    /**
     * Returns the version number representing the type of aggregator the proxy points to
     */
    function version() external view returns (uint256);

    /**
     * Returns price data about a specific round
     */
    function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

    /**
     * Returns price data from the latest round
     */
    function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

A interface expõe cinco funções: decimals, description, version, getRoundData e latestRoundData. Para mais detalhes, veja a referência da Chainlink.

Essas funções permitem consultar metadados do feed, verificar o par de ativos, e obter o preço mais recente ou de uma rodada específica.

Ativos suportados

A Phala obtém os feeds espelhando os feeds Chainlink do Ethereum Mainnet. Há contratos para a rede EVM demo e para o Ethereum Mainnet:

Interagir com feeds na rede EVM demo

  1. Conecte a MetaMask à rede EVM demo (veja o guia Implantar contratos com Remix) e certifique-se de que a MetaMask está nessa rede.
  2. Cole o contrato Aggregator em um novo arquivo no Remix e compile.
  3. Vá para Deploy and Run TransactionsENVIRONMENT = Injected Provider -- MetaMask.
  4. Selecione AggregatorV3Interface em CONTRACT.
  5. No campo At Address, insira o endereço do feed desejado (ex.: BTC/USD 0x89BC5048d634859aef743fF2152363c0e83a6a49) e clique em At Address.

Compilar contrato agregador Acessar contrato agregador

Para consultar:

  1. Expanda AggregatorV3Interface.
  2. Clique em decimals para ver quantas casas decimais o feed usa.
  3. Clique em description para verificar o par.
  4. Clique em latestRoundData para ver o preço mais recente (int256 answer).

Ver dados de preço

Para obter um preço legível, ajuste pelo valor de decimals(). Ex.: se o retorno for 5230364122303 e decimals=8, o preço é 52.303,64.

Se preferir outro feed (DAI, ETH etc.), basta repetir os passos usando o endereço correspondente da tabela de ativos suportados.

Caso esteja depurando valores inesperados, valide se o decimals() retornado confere com o esperado para aquele par. Divergências de escala são a causa mais comum de leituras “estranhas” no front-end.

Lançando feeds de preço em uma rede EVM

É fácil lançar feeds em uma rede EVM do Tanssi! As etapas a seguir funcionam para redes Trial e dedicadas em Dancelight. Este guia é demonstrativo; para produção, contate a equipe Phala.

Setup

Clone o repositório Phala Mirrored Price Feed e instale dependências:

cd mirrored-price-feed/ && yarn install

Crie o .env a partir do exemplo:

cp env.example .env

Edite o .env e insira a chave privada de uma conta financiada na sua rede e o RPC da sua rede. Se estiver na sua própria rede, financie uma conta de teste via Sudo (dados no Tanssi dApp). Os demais campos podem ficar em branco.

PRIVATE_KEY=INSERT_PRIVATE_KEY
RPC_URL=INSERT_YOUR_NETWORK_RPC_URL
VERIFIER_URL=
VERIFY_ADDRESS=

Nota

Nunca compartilhe frase semente ou chave privada. Este guia é apenas educacional.

Configurar script de implantação

Edite scripts/OffchainAggregator.s.sol. Ele recebe decimals (mantenha 8) e a descrição do feed (ex.: BTC / USD). Use exatamente as descrições suportadas listadas em Ativos suportados, ou o feed não funcionará.

OffchainAggregator.s.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console2} from "forge-std/Script.sol";
import {OffchainAggregator} from "../src/OffchainAggregator.sol";

contract OffchainAggregatorScript is Script {
    function setUp() public {}

    function run() public {
        vm.startBroadcast();
        OffchainAggregator aggregator = new OffchainAggregator(
          8,
          'BTC / USD'
        );
        console2.log(address(aggregator));
        vm.stopBroadcast();
    }
}

Em feeder.ts, insira os detalhes da sua cadeia (RPC, chainId). O array mainnetFeedContracts (endereços do Mainnet) permanece. Limpe aggregatorContracts por enquanto — mais adiante você adicionará os endereços implantados na sua rede.

feeder.ts
import {
  createPublicClient,
  http,
  parseAbi,
  createWalletClient,
  defineChain,
} from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import * as dotenv from 'dotenv';

dotenv.config();

const mainnetFeedContracts = {
  'AAVE-USD': '0x547a514d5e3769680Ce22B2361c10Ea13619e8a9',
  'CRV-USD': '0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f',
  'ETH-USD': '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419',
  'BTC-USD': '0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c',
  'DAI-USD': '0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9',
  'USDT-USD': '0x3E7d1eAB13ad0104d2750B8863b489D65364e32D',
  'USDC-USD': '0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6',
};

const aggregatorContracts = {};

const abi = parseAbi([
  'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
  'function transmit(uint80 _roundId, int192 _answer, uint64 _timestamp) external',
  'function getRoundData(uint80 _roundId) public view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
]);

// Insert your network details here
const chain = defineChain({
  id: INSERT_EVM_CHAIN_ID,
  name: 'dancelight-evm-network',
  rpcUrls: {
    default: {
      http: ['INSERT_RPC_URL'],
    },
    public: {
      http: ['INSERT_RPC_URL'],
    },
  },
});

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
});

const targetChainPublicClient = createPublicClient({
  chain,
  transport: http(),
});

async function getLatestRoundData(pair: string) {
  const address = mainnetFeedContracts[pair];
  if (!address) {
    throw new Error(`${pair} mainnet feed contract did not exist.`);
  }
  const data = await publicClient.readContract({
    address,
    abi,
    functionName: 'latestRoundData',
  });
  return data;
}

async function getRoundDataFromAggregator(pair: string, roundId: number) {
  const address = aggregatorContracts[pair];
  if (!address) {
    throw new Error(`${pair} aggregator contract did not exist.`);
  }
  const publicClient = createPublicClient({
    chain,
    transport: http(),
  });
  try {
    const data = await publicClient.readContract({
      address,
      abi,
      functionName: 'getRoundData',
      args: [roundId],
    });
    return data;
  } catch {}
}

async function updateFeed(
  walletClient: ReturnType<createWalletClient>,
  pair: string
) {
  if (!aggregatorContracts[pair]) {
    throw new Error(`${pair} aggregator contract did not exist.`);
  }
  const [roundId, answer, startedAt, updatedAt, answeredInRound] =
    await getLatestRoundData(pair);
  const aggregatorRoundId = Number(roundId & BigInt('0xFFFFFFFFFFFFFFFF'));
  const data = await getRoundDataFromAggregator(pair, aggregatorRoundId);
  if (data[1] === answer) {
    console.info(
      `${pair} aggregatorRoundId ${aggregatorRoundId} data exists: ${data}`
    );
    return;
  }

  const hash = await walletClient.writeContract({
    address: aggregatorContracts[pair],
    abi,
    functionName: 'transmit',
    args: [roundId, answer, startedAt],
  });
  await targetChainPublicClient.waitForTransactionReceipt({ hash });
  console.info(`${pair} updated, transmit tx hash: ${hash}`);
}

async function main() {
  if (!process.env.PRIVATE_KEY) {
    throw new Error('missing process.env.PRIVATE_KEY');
  }
  const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
  const walletClient = createWalletClient({
    chain,
    transport: http(),
    account,
  });
  for (const pair in aggregatorContracts) {
    await updateFeed(walletClient, pair);
  }
}

main()
  .then(() => process.exit(0))
  .catch((err) => {
    console.error(err);
    process.exit(1);
  });

Build e testes

yarn build
yarn test

Saída esperada:

yarn build yarn run v1.22.10 forge build [.] compiling No files changed, compilation skipped ✨ Done in 0.765. yarn test yarn run v1.22.10 forge test [.] compiling No files changed, compilation skipped Running 1 test for test/OffchainAggregator.t.sol:OffchainAggregatorTest [PASS] test_transmit() (gas: 60497) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.96ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) ✨ Done in 0.765.

Deploy

Para implantar o contrato agregador do par escolhido:

yarn deploy

Anote o endereço retornado.

Waiting for receipts. [O0:00:18]【######################################################】 1/1 receipts CO.0s) #####7796 ✅ [Success]Hash: Oxfb2f2dc6a35286c4595ce6e2bb64c93425b14c310a53f8224df0520666329fd ✅ Contract Address: OxBc788db88C3344a24706754c1203a267790D626 Block: 4049 Paid: 0.002392252 ETH (598063 gas * 4 gwei) Transactions saved to: /Users/tanssi/workspace/phalaMirrored/mirrored-price-feed/broadcast/OffchainAggregator.s.sol/7796/run-latest.json Sensitive values saved to: /Users/tanssi/workspace/phalaMirrored/mirrored-price-feed/cache/OffchainAggregator.s.sol/7796/run-latest.json ========================== ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. Total Paid: 0.002392252 ETH (598063 gas * avg 4 gwei) Transactions saved to: /Users/tanssi/workspace/phalaMirrored/mirrored-price-feed/broadcast/OffchainAggregator.s.sol/7796/run-latest.json Sensitive values saved to: /Users/kevin/workspace/phalaMirrored/mirrored-price-feed/cache/OffchainAggregator.s.sol/7796/run-latest.json ✨ Done in 30.765s.

Acessar o contrato agregador

No Remix, com a MetaMask na sua rede EVM, cole o endereço implantado em At Address. Expanda AggregatorV3Interface e clique em latestRoundData — inicialmente deve retornar 0 (sem preço atualizado ainda).

Acessar contrato agregador Saída do contrato implantado

Se ainda não tiver sua rede configurada na MetaMask, use o botão Add to MetaMask no dashboard do Tanssi dApp para adicioná-la rapidamente.

Disparar atualização de preço

Inclua o endereço do agregador em aggregatorContracts no feeder.ts:

const aggregatorContracts = {
  'BTC-USD': 'INSIRA_ENDERECO_DO_AGREGADOR',
}

Depois execute:

npx tsx feeder.ts
npx tsx feeder.ts BTC-USD updated, transmit tx hash: Oxf1797cfc5bd71e2d848b099cae197ff30dafb5f6947481a5ef6c69271e059a96

No Remix, chame latestRoundData novamente para ver o preço atualizado.

Ver preço após atualização

Para mais informações sobre uso da Phala para dados off-chain, veja os docs da Phala.

Essa abordagem completa (contrato + script + atualização via feeder) garante que os feeds fiquem sincronizados com as fontes da Chainlink no Mainnet e que sua rede Tanssi receba preços confiáveis.

As informações apresentadas aqui foram fornecidas por terceiros e estão disponíveis apenas para fins informativos gerais. A Tanssi não endossa nenhum projeto listado e descrito no Site de Documentação da Tanssi (https://docs.tanssi.network/). A Tanssi Foundation não garante a precisão, integridade ou utilidade dessas informações. Qualquer confiança depositada nelas é de sua exclusiva responsabilidade. A Tanssi Foundation se exime de toda responsabilidade decorrente de qualquer confiança que você ou qualquer outra pessoa possa ter em qualquer parte deste conteúdo. Todas as declarações e/ou opiniões expressas nesses materiais são de responsabilidade exclusiva da pessoa ou entidade que as fornece e não representam necessariamente a opinião da Tanssi Foundation. As informações aqui não devem ser interpretadas como aconselhamento profissional ou financeiro de qualquer tipo. Sempre busque orientação de um profissional devidamente qualificado em relação a qualquer assunto ou circunstância em particular. As informações aqui podem conter links ou integração com outros sites operados ou conteúdo fornecido por terceiros, e tais sites podem apontar para este site. A Tanssi Foundation não tem controle sobre esses sites ou seu conteúdo e não terá responsabilidade decorrente ou relacionada a eles. A existência de qualquer link não constitui endosso desses sites, de seu conteúdo ou de seus operadores. Esses links são fornecidos apenas para sua conveniência, e você isenta e exonera a Tanssi Foundation de qualquer responsabilidade decorrente do uso dessas informações ou das informações fornecidas por qualquer site ou serviço de terceiros.
Última atualização: 9 de dezembro de 2025
| Criada: 9 de dezembro de 2025