Skip to content

Commit

Permalink
feat: proxy colony incoming native tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
iamsamgibbs committed Jan 23, 2025
1 parent 1d120a4 commit fe1f789
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 37 deletions.
2 changes: 1 addition & 1 deletion amplify/backend/api/colonycdapp/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,7 @@ type Colony @model {
Native chain token claim (e.g., Token 0x0000...0000: ETH, xDAI, etc.)
This is not an array since only a single token type can be returned
"""
chainFundsClaim: ColonyChainFundsClaim
chainFundsClaim: [ColonyChainFundsClaim]
@function(name: "fetchColonyNativeFundsClaim-${env}")
"""
Type of the Colony (Regular or Metacolony)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[
{
"inputs": [
{
"internalType": "uint256",
"name": "_chainId",
"type": "uint256"
},
{
"internalType": "address",
"name": "_token",
Expand All @@ -25,6 +30,11 @@
"name": "_potId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_chainId",
"type": "uint256"
},
{
"internalType": "address",
"name": "_token",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
getProxyColonies: /* GraphQL */ `
query GetProxyColonies($colonyAddress: ID!) {
getProxyColoniesByColonyAddress(colonyAddress: $colonyAddress) {
items {
chainId
isActive
}
}
}
`,
};
111 changes: 103 additions & 8 deletions amplify/backend/function/fetchColonyNativeFundsClaim/src/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
const { constants, providers, Contract, BigNumber } = require('ethers');
const { graphqlRequest, getRpcUrlByChainId } = require('./utils');
const { getProxyColonies } = require('./graphql');
const basicColonyAbi = require('./basicColonyAbi.json');

let apiKey = 'da2-fakeApiId123456';
let graphqlURL = 'http://localhost:20002/graphql';

let rpcURL = 'http://network-contracts:8545'; // this needs to be extended to all supported networks

const setEnvVariables = async () => {
const ENV = process.env.ENV;
if (ENV === 'qa' || ENV === 'prod') {
const { getParams } = require('/opt/nodejs/getParams');
[rpcURL] = await getParams(['chainRpcEndpoint']);
[apiKey, graphqlURL, rpcURL] = await getParams([
'appsyncApiKey',
'graphqlUrl',
// @TODO: get this for all supported networks
'chainRpcEndpoint',
]);
}
};

Expand All @@ -18,6 +28,29 @@ exports.handler = async ({ source: { id: colonyAddress } }) => {
throw new Error('Unable to set environment variables. Reason:', e);
}

// Fetch proxy colony details
const getProxyColoniesResponse = await graphqlRequest(
getProxyColonies,
{ colonyAddress },
graphqlURL,
apiKey,
);

if (getProxyColoniesResponse.errors || !getProxyColoniesResponse.data) {
const [error] = getProxyColoniesResponse.errors;
throw new Error(
error?.message || 'Could not fetch user data from DynamoDB',
);
}

const { items: proxyColonies } =
getProxyColoniesResponse?.data?.getProxyColoniesByColonyAddress;

const activeProxyColonies = proxyColonies.filter(
(proxyColony) => proxyColony.isActive,
);

// Handle main chain
const provider = new providers.StaticJsonRpcProvider(rpcURL);

const providerNetwork = await provider.getNetwork();
Expand All @@ -40,35 +73,97 @@ exports.handler = async ({ source: { id: colonyAddress } }) => {
createdAtBlock: block,
};

const balance = await provider.getBalance(colonyAddress);
const balanceOnMainChain = await provider.getBalance(colonyAddress);

/*
* Short circuit early, before making more expensive calls
* If balance is 0, then no incoming transfers have been made
*/
if (balance.gt(0)) {
if (balanceOnMainChain.gt(0)) {
const colonyNonRewardsPotsTotal =
await lightColonyClient.getNonRewardPotsTotal(constants.AddressZero);
await lightColonyClient.getNonRewardPotsTotal(
chainId,
constants.AddressZero,
);
const colonyRewardsPotTotal = await lightColonyClient.getFundingPotBalance(
/*
* Root domain, since all initial transfers go in there
*/
0,
chainId,
constants.AddressZero,
);
const unclaimedBalance = balance
.sub(colonyNonRewardsPotsTotal)
.sub(colonyRewardsPotTotal);

if (unclaimedBalance.gt(0)) {
return {
colonyFundsClaim = {
...colonyFundsClaim,
amount: unclaimedBalance.toString(),
};
}
}

// If the balance is 0, or unclaimed balance is 0, still return a claim with amount zero.
// This is because we want to always show native chain tokens in the incoming funds table.
return colonyFundsClaim;
// Return early if no proxy colonies
if (activeProxyColonies.length < 1) {
// If the balance is 0, or unclaimed balance is 0, still return a claim with amount zero.
// This is because we want to always show native chain tokens in the incoming funds table.
return [colonyFundsClaim];
}

const proxyColonyClaims = await Promise.all(
activeProxyColonies.map(async (proxyColony) => {
const proxyChainId = proxyColony.chainId;
const proxyRpcURL = getRpcUrlByChainId(proxyChainId);
const proxyProvider = new providers.StaticJsonRpcProvider(proxyRpcURL);
const proxyBlock = await proxyProvider.getBlockNumber();
const proxyBalance = await proxyProvider.getBalance(colonyAddress);

const proxyColonyFundsClaim = {
__typeName: 'ColonyFundsClaim',
amount: BigNumber.from(0).toString(),
id: `${proxyChainId}_${constants.AddressZero}_0`,
createdAt: now,
updatedAt: now,
// @TODO: should this use the proxy chain block or main chain block?
// it is used to order claims in the incoming funds table
// but that doesn't really matter seeing as we sum them all together then display by token anyway
createdAtBlock: proxyBlock,
};

if (proxyBalance.gt(0)) {
const proxyColonyNonRewardsPotsTotal =
await lightColonyClient.getNonRewardPotsTotal(
proxyChainId,
constants.AddressZero,
);
const proxyColonyRewardsPotTotal =
await lightColonyClient.getFundingPotBalance(
/*
* Root domain, since all initial transfers go in there
*/
0,
proxyChainId,
constants.AddressZero,
);
const unclaimedBalance = proxyBalance
.sub(proxyColonyNonRewardsPotsTotal)
.sub(proxyColonyRewardsPotTotal);

if (unclaimedBalance.gt(0)) {
return {
...proxyColonyFundsClaim,
amount: unclaimedBalance.toString(),
};
}
}

// If the balance is 0, or unclaimed balance is 0, still return a claim with amount zero.
// This is because we want to always show native chain tokens in the incoming funds table.
return proxyColonyFundsClaim;
}),
);

return [colonyFundsClaim].concat(proxyColonyClaims);
};
62 changes: 62 additions & 0 deletions amplify/backend/function/fetchColonyNativeFundsClaim/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const fetch = require('cross-fetch');

const graphqlRequest = async (queryOrMutation, variables, url, authKey) => {
const options = {
method: 'POST',
headers: {
'x-api-key': authKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: queryOrMutation,
variables,
}),
};

let body;
let response;

try {
response = await fetch(url, options);
body = await response.json();
return body;
} catch (error) {
/*
* Something went wrong... obviously
*/
console.error(error);
return null;
}
};

const getRpcUrlByChainId = (chainId) => {
const ENV = process.env.ENV;

switch (chainId) {
case '265669101': {
if (ENV === 'qa' || ENV === 'prod') {
// @TODO: get this from params
return 'http://actual-rpc-endpoint:8545';
}
return 'http://network-contracts-remote:8545';
}

case '265669102': {
if (ENV === 'qa' || ENV === 'prod') {
// @TODO: get this from params
return 'http://actual-rpc-endpoint:8545';
}
return 'http://network-contracts-remote-2:8545';
}

default: {
console.error(`RPC URL not found for chain ID: ${chainId}`);
return null;
}
}
};

module.exports = {
graphqlRequest,
getRpcUrlByChainId,
};
2 changes: 1 addition & 1 deletion docker/colony-cdapp-dev-env-network
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM colony-cdapp-dev-env/base:latest

ENV NETWORK_HASH=0c713cf0a2614417e3cf7bacb2f718a710148937
ENV NETWORK_HASH=184361d8af40fd53e6de09a49cd1be94fb7ba363

# Declare volumes to set up metadata
VOLUME [ "/colonyCDapp/amplify/mock-data" ]
Expand Down
1 change: 1 addition & 0 deletions src/apollo/cache/colony/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const tokensFieldCache = {
type: TokenType.Colony,
},
},
// @TODO: add other tokens here? or add chainId to this?
];

const colonyTokensResolved = baseTokens.items.map((colonyToken) => {
Expand Down
26 changes: 13 additions & 13 deletions src/graphql/generated.ts

Large diffs are not rendered by default.

31 changes: 17 additions & 14 deletions src/hooks/useColonyFundsClaims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ const useColonyFundsClaims = (): ColonyClaims[] => {
return [];
}

const chainClaimWithToken: ColonyChainClaimWithToken | null = chainFundsClaim
? {
...chainFundsClaim,
token: tokens?.items?.find(
(token) => token?.token?.tokenAddress === ADDRESS_ZERO,
)?.token,
}
: null;
const chainClaimWithToken: ColonyChainClaimWithToken[] = chainFundsClaim
? chainFundsClaim.filter(notNull).map((item): ColonyChainClaimWithToken => {
return {
...item,
// @TODO: get chain token
token: tokens?.items?.find(
(token) => token?.token?.tokenAddress === ADDRESS_ZERO,
)?.token,
};
})
: [];

/*
* Claims data needs to be merged, both ERC20's and Native Chain Tokens
Expand All @@ -41,12 +44,12 @@ const useColonyFundsClaims = (): ColonyClaims[] => {
* to do the sorting / merging here, rather than at the time we fetch data.
* This kinda sucks!
*/
return [...claims, chainClaimWithToken]
.filter(notNull)
.sort(
(first, second) =>
(second?.createdAtBlock || 0) - (first?.createdAtBlock || 0),
);
return [...claims, ...chainClaimWithToken].filter(notNull).sort(
(first, second) =>
// @TODO: native token claims are based off of the proxy chain block
// should it be based off of the main chain block instead?
(second?.createdAtBlock || 0) - (first?.createdAtBlock || 0),
);
};

export default useColonyFundsClaims;

0 comments on commit fe1f789

Please sign in to comment.