Skip to content

Commit

Permalink
Support DAppNode airdrop (#4077)
Browse files Browse the repository at this point in the history
* Add shared logic for airdrop claims

* Add DAppNode claim to action panel

* Add missing whitespace
  • Loading branch information
FrederikBolding authored Aug 9, 2021
1 parent 08712fa commit 4408ae3
Show file tree
Hide file tree
Showing 18 changed files with 213 additions and 79 deletions.
81 changes: 81 additions & 0 deletions src/assets/images/node-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 27 additions & 3 deletions src/components/ActionsPanel/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { add, isBefore } from 'date-fns';
import { TIcon } from '@components';
import {
ANTv1UUID,
DAPPNODE_AIRDROP_LINK,
ETHUUID,
EXT_URLS,
FAUCET_NETWORKS,
Expand Down Expand Up @@ -106,10 +107,10 @@ export const actionTemplates: ActionTemplate[] = [
},
{
name: ACTION_NAME.CLAIM_UNI,
heading: translateRaw('CLAIM_UNI_ACTION_HEADING'),
heading: translateRaw('CLAIM_TOKENS_ACTION_HEADING', { $token: 'UNI' }),
icon: 'uni-logo',
subHeading: ClaimSubHead,
body: [translate('CLAIM_UNI_ACTION_BODY')],
body: [translate('CLAIM_TOKENS_ACTION_BODY', { $token: 'UNI' })],
filter: ({ claims }: ActionFilters) =>
claims[ClaimType.UNI]?.some((c) => c.state === ClaimState.UNCLAIMED),
priority: 30,
Expand All @@ -120,13 +121,36 @@ export const actionTemplates: ActionTemplate[] = [
button: {
component: ActionButton,
props: {
content: translateRaw('CLAIM_UNI_ACTION_BUTTON'),
content: translateRaw('CLAIM_TOKENS_ACTION_BUTTON'),
to: UNISWAP_LINK,
external: true
}
},
category: ACTION_CATEGORIES.THIRD_PARTY
},
{
name: ACTION_NAME.CLAIM_DAPPNODE,
heading: translateRaw('CLAIM_TOKENS_ACTION_HEADING', { $token: 'NODE' }),
icon: 'node-logo',
subHeading: ClaimSubHead,
body: [translate('CLAIM_TOKENS_ACTION_BODY', { $token: 'NODE' })],
filter: ({ claims }: ActionFilters) =>
claims[ClaimType.NODE]?.some((c) => c.state === ClaimState.UNCLAIMED),
priority: 30,
Component: ClaimTable,
props: {
type: ClaimType.NODE
},
button: {
component: ActionButton,
props: {
content: translateRaw('CLAIM_TOKENS_ACTION_BUTTON'),
to: DAPPNODE_AIRDROP_LINK,
external: true
}
},
category: ACTION_CATEGORIES.THIRD_PARTY
},
{
name: ACTION_NAME.MIGRATE_LEND,
heading: translateRaw('MIGRATE_LEND_ACTION_HEADING'),
Expand Down
4 changes: 3 additions & 1 deletion src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ import warning from '@assets/images/icn-warning.svg';
import lendLogo from '@assets/images/lend-logo.png';
import linkOutIcon from '@assets/images/link-out.svg';
import membership from '@assets/images/membership/membership-none.svg';
import nodeLogo from '@assets/images/node-logo.svg';
import repLogo from '@assets/images/rep-logo.svg';
import swap from '@assets/images/swap copy.svg';
import uniLogo from '@assets/images/uni-logo.png';
Expand Down Expand Up @@ -270,7 +271,8 @@ const pngIcons = {
'uni-logo': uniLogo,
'lend-logo': lendLogo,
'ant-logo': antLogo,
'gol-logo': golemLogo
'gol-logo': golemLogo,
'node-logo': nodeLogo
};

type SvgIcons = keyof typeof svgIcons;
Expand Down
2 changes: 2 additions & 0 deletions src/config/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export const BUY_MYCRYPTO_WEBSITE = 'https://buy.mycrypto.com' as TURL;
export const MOONPAY_SIGNER_API = 'https://moonpay.mycryptoapi.com/sign';
export const UNISWAP_UNI_CLAIM_API = 'https://uni.mycryptoapi.com/claims';
export const UNISWAP_TOKEN_DISTRIBUTOR = '0x090D4613473dEE047c3f2706764f49E0821D256e';
export const DAPPNODE_CLAIM_API = 'https://dappnode.mycryptoapi.com/claims';
export const DAPPNODE_TOKEN_DISTRIBUTOR = '0x87d6180b65ad76a9443064dcd1596388fcc3ee2a';

export const LETS_ENCRYPT_URL = 'https://letsencrypt.org/';

Expand Down
14 changes: 1 addition & 13 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,7 @@ export * from './data';
export { DEMO_SETTINGS } from './demo';
export * from './donations';
export * from './addressMessages';
export {
EXT_URLS,
partnerLinks,
productLinks,
socialMediaLinks,
DOWNLOAD_MYCRYPTO_LINK,
SUBSCRIBE_NEWSLETTER_LINK,
UNISWAP_LINK,
MYC_WINTER_LINK,
TWEET_LINK,
MYCRYPTO_FAUCET_LINK,
MYCRYPTO_PROD_LINK
} from './links';
export * from './links';
export * from './constants';
export * from './uuids';
export { ETHSCAN_NETWORKS } from './ethScan';
Expand Down
1 change: 1 addition & 0 deletions src/config/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const partnerLinks: Link[] = [
];

export const UNISWAP_LINK = 'https://app.uniswap.org/';
export const DAPPNODE_AIRDROP_LINK = 'https://app.dappnode.io/nodedrop';

export const MYC_WINTER_LINK = 'https://winter.mycrypto.com';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { waitFor } from '@testing-library/react';
import mockAxios from 'jest-mock-axios';

import { UNISWAP_UNI_CLAIM_API } from '@config';
import { fAccount, fNetwork } from '@fixtures';
import { ClaimState, ITxValue } from '@types';
import { ClaimState, ClaimType, ITxValue } from '@types';

import { UniswapService } from '.';
import { ClaimsService } from '.';

jest.mock('@vendor', () => ({
...jest.requireActual('@vendor'),
Expand All @@ -26,15 +27,21 @@ const mockClaim = {
}
};

describe('UniswapService', () => {
describe('ClaimsService', () => {
afterEach(() => {
mockAxios.reset();
});
it('can get claims', async () => {
const addresses = [fAccount.address];
const claims = UniswapService.instance.getClaims(addresses);
const claims = ClaimsService.instance.getClaims(ClaimType.UNI, addresses);

await waitFor(() => expect(mockAxios.post).toHaveBeenCalledWith('', { addresses }));
await waitFor(() =>
expect(mockAxios.post).toHaveBeenCalledWith(
'',
{ addresses },
{ baseURL: UNISWAP_UNI_CLAIM_API }
)
);

mockAxios.mockResponse({
data: { claims: mockClaim }
Expand All @@ -45,7 +52,7 @@ describe('UniswapService', () => {
});

it('can check whether claims are still valid', async () => {
const claims = await UniswapService.instance.isClaimed(fNetwork, mockClaim);
const claims = await ClaimsService.instance.isClaimed(fNetwork, ClaimType.UNI, mockClaim);
expect(claims).toStrictEqual([
{
address: fAccount.address,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,20 @@
import { AxiosInstance } from 'axios';
import { toChecksumAddress } from 'ethereumjs-util';

import { UNISWAP_TOKEN_DISTRIBUTOR, UNISWAP_UNI_CLAIM_API } from '@config/data';
import { UNISWAP_UNI_CLAIM_API } from '@config/data';
import { ApiService } from '@services/ApiService';
import { ProviderHandler } from '@services/EthService';
import { UniDistributor } from '@services/EthService/contracts';
import { ClaimResult, ClaimState, ITxValue, Network, TAddress } from '@types';
import { ClaimResult, ClaimState, ClaimType, Network, TAddress } from '@types';
import { mapAsync } from '@utils/asyncFilter';

let instantiated = false;

interface Response {
success: boolean;
claims: Record<string, UniClaim | null>;
}
import { CLAIM_CONFIG } from './config';
import { Claim, Response } from './types';

interface UniClaim {
Index: number;
Amount: ITxValue; // HEX
// proof: ITxData[]; @todo
Flags: {
IsSOCKS: boolean;
ISLP: boolean;
IsUser: boolean;
};
}
let instantiated = false;

export default class UniswapService {
public static instance = new UniswapService();
export default class ClaimsService {
public static instance = new ClaimsService();

private service: AxiosInstance = ApiService.generateInstance({
baseURL: UNISWAP_UNI_CLAIM_API,
Expand All @@ -36,17 +23,21 @@ export default class UniswapService {

constructor() {
if (instantiated) {
throw new Error(`UniswapService has already been instantiated.`);
throw new Error(`ClaimsService has already been instantiated.`);
} else {
instantiated = true;
}
}

public getClaims(addresses: TAddress[]) {
public getClaims(type: ClaimType, addresses: TAddress[]) {
return this.service
.post('', {
addresses: addresses.map((a) => toChecksumAddress(a))
})
.post(
'',
{
addresses: addresses.map((a) => toChecksumAddress(a))
},
{ baseURL: CLAIM_CONFIG[type].api }
)
.then((res) => res.data)
.then(({ claims }: Response) => {
return claims;
Expand All @@ -59,14 +50,15 @@ export default class UniswapService {

public isClaimed(
network: Network,
claims: Record<string, UniClaim | null>
type: ClaimType,
claims: Record<string, Claim | null>
): Promise<ClaimResult[]> {
const provider = new ProviderHandler(network);
return mapAsync(Object.entries(claims), async ([address, claim]) => {
if (claim !== null) {
const claimed = await provider
.call({
to: UNISWAP_TOKEN_DISTRIBUTOR,
to: CLAIM_CONFIG[type].tokenDistributor,
data: UniDistributor.isClaimed.encodeInput({ index: claim.Index })
})
.then((data) => UniDistributor.isClaimed.decodeOutput(data))
Expand Down
18 changes: 18 additions & 0 deletions src/services/ApiService/Claims/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
DAPPNODE_CLAIM_API,
DAPPNODE_TOKEN_DISTRIBUTOR,
UNISWAP_TOKEN_DISTRIBUTOR,
UNISWAP_UNI_CLAIM_API
} from '@config';
import { ClaimType } from '@types';

export const CLAIM_CONFIG = {
[ClaimType.UNI]: {
api: UNISWAP_UNI_CLAIM_API,
tokenDistributor: UNISWAP_TOKEN_DISTRIBUTOR
},
[ClaimType.NODE]: {
api: DAPPNODE_CLAIM_API,
tokenDistributor: DAPPNODE_TOKEN_DISTRIBUTOR
}
};
1 change: 1 addition & 0 deletions src/services/ApiService/Claims/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ClaimsService } from './Claims';
11 changes: 11 additions & 0 deletions src/services/ApiService/Claims/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ITxValue } from '@types';

export interface Response {
success: boolean;
claims: Record<string, Claim | null>;
}

export interface Claim {
Index: number;
Amount: ITxValue; // HEX
}
1 change: 0 additions & 1 deletion src/services/ApiService/Uniswap/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/services/ApiService/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export {
CryptoScamDBService
} from './CryptoScamDB';
export { MoonpaySignerService } from './MoonpaySigner';
export { UniswapService } from './Uniswap';
export * from './Claims';
export * from './constants';
export { ENSService } from './Ens';
export { CustomAssetService } from './CustomAsset';
Expand Down
8 changes: 4 additions & 4 deletions src/services/Store/store/claims.slice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { throwError } from 'redux-saga-test-plan/providers';
import { expectSaga, mockAppState } from 'test-utils';

import { fAccounts, fNetworks } from '@fixtures';
import { UniswapService } from '@services/ApiService';
import { ClaimsService } from '@services/ApiService';
import { ClaimState, ClaimType, ITxValue, TAddress } from '@types';

import slice, { claimsSaga, fetchClaims, initialState } from './claims.slice';
Expand Down Expand Up @@ -56,8 +56,8 @@ describe('claimsSaga()', () => {
})
)
.provide([
[call.fn(UniswapService.instance.getClaims), []],
[call.fn(UniswapService.instance.isClaimed), claims]
[call.fn(ClaimsService.instance.getClaims), []],
[call.fn(ClaimsService.instance.isClaimed), claims]
])
.put(setClaims({ type: ClaimType.UNI, claims }))
.dispatch(fetchClaims())
Expand All @@ -68,7 +68,7 @@ describe('claimsSaga()', () => {
const error = new Error('error');
return expectSaga(claimsSaga)
.withState(mockAppState({ accounts: fAccounts, networks: fNetworks }))
.provide([[call.fn(UniswapService.instance.getClaims), throwError(error)]])
.provide([[call.fn(ClaimsService.instance.getClaims), throwError(error)]])
.put(fetchError())
.dispatch(fetchClaims())
.silentRun();
Expand Down
44 changes: 25 additions & 19 deletions src/services/Store/store/claims.slice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createAction, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { all, call, put, select, takeLatest } from 'redux-saga/effects';

import { UniswapService } from '@services/ApiService';
import { ClaimsService } from '@services/ApiService';
import { ClaimResult, ClaimType, Network, StoreAccount } from '@types';

import {
Expand Down Expand Up @@ -56,13 +56,13 @@ export function* claimsSaga() {
resetAndCreateManyAccounts.type,
destroyAccount.type
],
fetchUniClaimsWorker
fetchClaimsWorker
),
takeLatest(fetchClaims.type, fetchUniClaimsWorker)
takeLatest(fetchClaims.type, fetchClaimsWorker)
]);
}

export function* fetchUniClaimsWorker() {
export function* fetchClaimsWorker() {
const accounts: StoreAccount[] = yield select(getAccounts);

const filteredAccounts = accounts.filter((a) => a.networkId === 'Ethereum');
Expand All @@ -71,21 +71,27 @@ export function* fetchUniClaimsWorker() {

const network: Network = yield select(selectDefaultNetwork);

try {
const rawClaims = yield call(
[UniswapService.instance, UniswapService.instance.getClaims],
filteredAccounts.map((a) => a.address)
);

const claims = yield call(
[UniswapService.instance, UniswapService.instance.isClaimed],
network,
rawClaims
);

yield put(slice.actions.setClaims({ type: ClaimType.UNI, claims }));
} catch (err) {
yield put(slice.actions.fetchError());
const types = Object.values(ClaimType);

for (const type of types) {
try {
const rawClaims = yield call(
[ClaimsService.instance, ClaimsService.instance.getClaims],
type,
filteredAccounts.map((a) => a.address)
);

const claims = yield call(
[ClaimsService.instance, ClaimsService.instance.isClaimed],
network,
type,
rawClaims
);

yield put(slice.actions.setClaims({ type, claims }));
} catch (err) {
yield put(slice.actions.fetchError());
}
}
}

Expand Down
Loading

0 comments on commit 4408ae3

Please sign in to comment.