Wdrażanie przykładowej aplikacji (Uniswap) do Moonbeam

Lucas | Eaglenode
7 min readSep 18, 2021

Moonbeam to inteligentna platforma kontraktowa w ekosystemie Polkadot, która zapewnia płynne środowisko podobne do Ethereum. Ta kompatybilność ma na celu ułatwienie ponownego wdrażania istniejących aplikacji Ethereum do Moonbeam (a tym samym Polkadot) bez znaczącej zmiany bazy kodu.

Jako przykład tego, jak wygląda integracja, stworzyłem przewodnik po tym procesie. Należy pamiętać, że ten przewodnik wdrażania służy wyłącznie do celów edukacyjnych, aby zademonstrować, jak wdrażać aplikacje w Moonbeam. W tym artykule poprowadzę Cię przez przeniesienie Uniswap V2 do Moonbase Alpha TestNet. Wyjaśnię również zmiany potrzebne do uzyskania w pełni funkcjonalnego Uniswap V2 DEX z podstawową funkcjonalnością Moonbeam. Jeśli wolisz przejść dalej, możesz sprawdzić wersję Uniswap V2 działającą obecnie w bazie Moonbase Alpha. W Moonbase Alpha znajduje się również faucet tokenów ERC20.

Napisałem również artykuł wyjaśniający, czym jest Uniswap i jak to działa, jeśli jeszcze nie jesteś zaznajomiony.

Migracja Uniswap V2 do Moonbase Alpha lub Moonbeam Standalone Node

Wdrażanie kontraktów Uniswap V2 do Moonbeam

Konkretny przykład jest lepszy niż 1000 słów, więc ta sekcja ilustruje proces wdrażania wszystkich niezbędnych kontraktów Uniswap V2 do Moonbeam.

W tym celu przygotowano repozytorium Github. Folder „uniswap-contracts-moonbeam” zawiera konfigurację Hardhat, która zawiera wszystkie niezbędne pliki umów i skrypt wdrażania dla następujących kontraktów (niezbędne do uruchomienia Uniswap):

  • WETH: eter opakowany w interfejs tokena ERC20 (w interfejsie będzie się to nazywać opakowanym DEV lub WDEV)
  • UniswapV2Factory: wymagane tylko dane wejściowe to adres, który może aktywować opłatę na poziomie protokołu (dane wejściowe są potrzebne, ale nie są używane w tym przykładzie)
  • UniswapV2Router02: wymaga adresu obu umów WETH i UniswapV2Factory
  • Multicall: agreguje wyniki z wielu wywołań funkcji stałej umowy, zmniejszając liczbę oddzielnych żądań JSON RPC, które należy wysłać. Jest to wymagane przez interfejs Uniswap
  • Dwa tokeny ERC20: nie są wymagane, ale zostały dołączone, aby mieć kilka tokenów, z którymi można zacząć się bawić

Rozpoczęcie jest tak proste, jak klonowanie repozytorium, instalowanie zależności i wdrażanie kontraktów z Hardhat. Więc najpierw zacznijmy klonować repozytorium i instalować zależności:

git clone https://github.com/PureStake/moonbeam-uniswap 
cd moonbeam-uniswap/uniswap-contracts-moonbeam
npm i

W tym folderze znajdziesz plik „hardhat-config.js”, który zawiera dwie wstępnie skonfigurowane sieci: autonomiczny węzeł Moonbeam i sieć Moonbase Alpha TestNet. W tym miejscu upewnij się, że zmodyfikowałeś `privateKey2` dla klucza prywatnego, którego chcesz użyć do wdrożenia kontraktów w sieci TestNet.

W tym przykładzie będę wdrażać kontrakty w samodzielnym węźle Moonbeam. Możesz rozkręcić swój własny, postępując zgodnie z tym samouczkiem. Mając uruchomioną nową instancję autonomicznego węzła, możemy uruchomić nasz skrypt:

npx hardhat run --network standalone scripts/deploy-factory.js

Uwaga: Aby upewnić się, że interfejs Uniswap zawarty w tym repozytorium działa z samodzielnym węzłem Moonbeam, musisz wdrożyć kontrakty na nowej instancji przy użyciu dostarczonego klucza prywatnego. Kontrakty zostaną wdrożone do określonych adresów, które są już skonfigurowane w interfejsie i SDK.

Używając tylko prostego skryptu Hardhat + Ethers.js, wdrożyliśmy wszystkie niezbędne kontrakty Uniswap V2 na samodzielnym węźle Moonbeam. Zauważ, że na pierwszy rzut oka żadne modyfikacje nie były wymagane na poziomie umowy.

Jednak Uniswap używa skrótu init_code, który został zmieniony w kontrakcie UniswapV2Library. Pamiętasz kod operacji create2? Cóż, zmiana jest z tym powiązana. Ten skrót init_code jest używany do obliczania adresu puli (utworzonej przez opcode) przez podanie tylko adresów dwóch tokenów ERC20. Adres można obliczyć za pomocą następującego wzoru:

Gdzie sól jest taka sama jak ta pokazana w kodzie UniswapV2Factory (dotyczy obu adresów tokenów). Skrót init_code to ostatni bit równania (keccak256(UniswapV2Pair.bytecode)), czyli skrót keccak256 kodu bajtowego kontraktu UniswapV2Pair. Jeśli zostanie podany nieprawidłowy kod init_code, inteligentne kontrakty (i interfejs) nie będą w stanie obliczyć poprawnego adresu puli. Kod ini_code można uzyskać na różne sposoby. Na przykład ręczne pobieranie kodu bajtowego w Remixie i obliczanie skrótu keccak 256.

Zmieniony kod znajduje się w funkcji pairFor umowy UniswapV2Library:

// calculates the CREATE2 address for a pair without making any external calls
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'01429e880a7972ebfbba904a5bbe32a816e78273e4b
38ffa6bdeaebce8adba7c' // init code hash
))));
}

Modyfikacja ta nie jest jednak związana z wdrożeniem kontraktu na Moonbeam, a nie na Ethereum, ale jest konsekwencją działania opcode create2.

Dostosowanie interfejsu Uniswap V2 do obsługi Moonbeam

Interfejs Uniswap V2 jest używany jako oprogramowanie pośredniczące między inteligentnymi kontraktami a użytkownikiem. Interfejs opiera się na zewnętrznych dostawcach (takich jak MetaMask) i bibliotece ethers.js w celu połączenia i interakcji z łańcuchem bloków, odpowiednio.

Wdrożenie inteligentnych kontraktów do Moonbeam (w tym przypadku samodzielnego węzła) było dość prostym zadaniem, ponieważ zapewnia pełne środowisko podobne do Ethereum. Dostosowanie interfejsu do współpracy z naszym wdrożeniem było nieco bardziej skomplikowane. Dzieje się tak, ponieważ interfejs ma dwa główne czynniki, które ograniczają jego implementację w nowym łańcuchu bloków: identyfikator łańcucha sieci i adresy wdrożonych kontraktów. Co więcej, Uniswap SDK (pakiet, od którego zależy interfejs) również musi zostać zmodyfikowany z tych samych powodów (następna sekcja).

Zmodyfikowany interfejs znajduje się w tym repozytorium Github, w folderze „uniswap-interface-moonbeam”. Rozpoczęcie pracy jest dość proste: sklonuj repozytorium, zainstaluj zależności i uruchom instancję interfejsu.

git clone https://github.com/PureStake/moonbeam-uniswap
cd moonbeam-uniswap/uniswap-interface-moonbeam
npm i
npm start

Zwróć uwagę, że interfejs (i SDK) mają zakodowane na stałe adresy kontraktów. Jeśli używasz nowego węzła autonomicznego i wdrażasz kontrakty z dostarczoną konfiguracją Hardhat, powinno to działać. Jeśli jednak wdrożysz kontrakty w TestNet, musisz zmodyfikować adresy zarówno w interfejsie, jak i w zestawie SDK.

Poniższe sekcje zagłębiają się w bardziej szczegółowy podział rzeczywistych zmian, które miały miejsce.

Włączanie Chain ID

Identyfikator łańcucha został wprowadzony w EIP-155. Powstał, aby zapobiec atakom powtórek między łańcuchami ETH i ETC, które współdzielą ten sam identyfikator sieci. Argument identyfikatora łańcucha jest zawarty w podpisie transakcji, dzięki czemu dwie identyczne transakcje będą miały różne wartości podpisu v-r-s. Niektóre wartości identyfikatora łańcucha obejmują:

Interfejs Uniswap V2 ma wbudowane pewne predefiniowane identyfikatory łańcucha, dzięki czemu po połączeniu interfejsu z łańcuchem bloków za pośrednictwem dostawcy sprawdza, czy protokół obsługuje sieć, z którą się łączysz.

Oba identyfikatory łańcucha związane z Moonbeamem zostały dodane do następujących plików (wewnątrz folderu „uniswap-interface-moonbeam”):

  • ./src/connectors/index.ts: dodaj odpowiednie identyfikatory łańcucha wewnątrz tablicy „supportedChainIds”:
export const injected = new InjectedConnector({
supportedChainIds: [1281, 1287]
//old supportedChainIds: [1, 3, 4, 5, 42]
})
  • ./src/components/Header/index.tsx: dodaj identyfikatory łańcucha i etykiety sieciowe zgodnie z odpowiednim formatem wewnątrz „NETWORK_LABELS”:
const WDEV_ONLY: ChainTokenList = {
[ChainId.MAINNET]: [WDEV[ChainId.MAINNET]],
[ChainId.STANDALONE]: [WDEV[ChainId.STANDALONE]],
[ChainId.MOONBASE]: [WDEV[ChainId.MOONBASE]]
}
  • ./src/constants/index.ts: dodaj identyfikatory łańcucha zgodnie z odpowiednim formatem wewnątrz „WDEV_ONLY: ChainTokenList”:
const WDEV_ONLY: ChainTokenList = {
[ChainId.MAINNET]: [WDEV[ChainId.MAINNET]],
[ChainId.STANDALONE]: [WDEV[ChainId.STANDALONE]],
[ChainId.MOONBASE]: [WDEV[ChainId.MOONBASE]]
}
  • ./src/state/lists/hooks.ts: dodaj identyfikatory łańcucha zgodnie z odpowiednim formatem wewnątrz „EMPTY_LIST: TokenAddressMap”:
const EMPTY_LIST: TokenAddressMap = {
[ChainId.MAINNET]: {},
[ChainId.STANDALONE]: {},
[ChainId.MOONBASE]: {}
}

W poprzednich fragmentach kodu obiekt ChainId.* jest importowany z zestawu SDK.

Dodanie Nowego Adresu Kontraktu

Jak można się spodziewać, interfejs Uniswap jest skonfigurowany do pracy z adresami kontraktów wdrożonych w Ethereum. Dlatego należy określić nowe adresy.

Celem było, aby interfejs działał zarówno z nowym samodzielnym węzłem Moonbeam, jak i z siecią Moonbase Alpha TestNet. W związku z tym wprowadzono pewne modyfikacje, aby adres był zależny od identyfikatora sieci, do której podłączony jest dostawca. Pliki, które zostały zmodyfikowane (w folderze „uniswap-interface-moonbeam”) są wymienione poniżej. Zauważ, że każdy plik importuje plik `moonbase_address.json`, który znajduje się w folderze `./src`, który zawiera adresy wdrożenia w Moonbase Alpha.

  • ./src/constants/index.ts: dodaj odpowiedni adres routera. Zmienna routerv2 jest importowana
export const ROUTER_ADDRESS: { [key: string]: string } = {
[ChainId.STANDALONE]: '0x42e2EE7Ba8975c473157634Ac2AF4098190fc741',
[ChainId.MOONBASE]: routerv2
}
  • ./src/constants/multicall/index.tsx: dodaj odpowiedni adres multicall
const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441',
[ChainId.STANDALONE]: '0xF8cef78E923919054037a1D03662bBD884fF4edf',
[ChainId.MOONBASE]: multicall
}
  • ./src/state/swap/hooks.ts: dodaj odpowiednie adresy fabryczne i routera w „BAD_RECIPIENT_ADDRESS”. Do celów testowych ten parametr nie jest wymagany
const BAD_RECIPIENT_ADDRESSES: string[] = [
factory, // v2 factory
routerv2 // v2 router 02
]

Inne Zmiany

Wszystko związane z Uniswap V1 zostało usunięte z plików, ponieważ w tym przykładzie nie użyto żadnego wdrożenia V1.

Inne zmiany obejmują również:
Logo: plik w ./src/assets/images/mainlogo.png
Linki: plik w ./src/components/Menu/index.tsx

<MenuItem id="link" href="https://moonbeam.network/">
<Home size={14} />
{t('Website')}
</MenuItem>
<MenuItem id="link" href="https://discord.gg/PfpUATX">
<MessageCircle size={14} />
{t('discord')}
</MenuItem>
<MenuItem id="link" href="https://github.com/PureStake/moonbeam">
<Code size={14} />
{t('code')}
</MenuItem>

Adaptacja Uniswap SDK do obsługi Moonbeam

Zestaw SDK zawiera dodatkowe informacje używane przez interfejs jako pakiet NPM. Zmodyfikowany pakiet SDK jest zawarty w tym repozytorium Github, w folderze „uniswap-sdk-moonbeam”.

Dostarczony folder SDK działa po wyjęciu z pudełka z samodzielnym wdrożeniem umowy Moonbeam za pomocą opisanej wcześniej konfiguracji Hardhat. Zawiera również adresy kontraktów wdrożonych w Moonbase Alpha TestNet. To wszystko jest spakowane i opublikowane jako pakiet NPM o nazwie „moonbeamswap”.

Przed zbudowaniem pakietu NPM należy zmodyfikować wymienione poniżej pliki (wewnątrz folderu „uniswap-sdk-moonbeam”). Zauważ, że każdy plik importuje plik `moonbase_address.json`, który znajduje się w folderze `./src` i zawiera adresy wdrożenia w Moonbase Alpha:

  • ./src/constants.ts: dodaj odpowiednie identyfikatory łańcucha wewnątrz wyliczenia „ChainId”, zmień adres fabryczny i zmodyfikuj skrót kodu init_code
export enum ChainId {
MAINNET = 1,
STANDALONE = 1281,
MOONBASE = 1287
}
...
export const FACTORY_ADDRESS: { [key: string]: string } = {
[ChainId.STANDALONE]: '0x5c4242beB94dE30b922f57241f1D02f36e906915',
[ChainId.MOONBASE]: factory
}
...
export const INIT_CODE_HASH = '0x01429e880a7972ebfbba904a5bbe32a816e78273e4b38ffa6bdeaebce8adba7c'
  • ./src/entities/token.ts: dodaj adres kontraktu tokena WETH (w tym przypadku WDEV):
export const WDEV = {
[ChainId.MAINNET]: new Token(
ChainId.MAINNET,
'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
18,
'WETH',
'Wrapped Ether'
),
[ChainId.STANDALONE]: new Token(
ChainId.STANDALONE,
'0xC2Bf5F29a4384b1aB0C063e1c666f02121B6084a',
18,
'WDEV',
'Wrapped Dev'
),
[ChainId.MOONBASE]: new Token(ChainId.MOONBASE, WETH, 18, 'WDEV', 'Wrapped Dev')
}

Po zmodyfikowaniu wszystkich plików zmień nazwę pakietu, wersję i opis w pliku `package.json`. Kiedy będziesz gotowy, zaloguj się na swoje konto npm, uruchom polecenie publikowania:

npm login #enter credentials 
npm publish

Jeśli zamierzasz uruchomić swój niestandardowy pakiet w interfejsie, upewnij się, że dodałeś go jako zależność w pliku `package.json` folderu interfejsu.

Dowiedz się więcej o Moonbeam

Jeśli jesteś zainteresowany projektem Moonbeam i chcesz dowiedzieć się więcej, zapisz się do naszego newslettera i śledź nas w mediach społecznościowych.

--

--