From 142a5a58d5f5be49a45df255e9b8eac33c06df11 Mon Sep 17 00:00:00 2001 From: Jakub Zajac Date: Thu, 26 Mar 2026 16:52:36 +0000 Subject: [PATCH 1/7] Add getClientSecrets lambda --- .env.example | 4 - .../api/colonycdapp/schema/schema.graphql | 8 + amplify/backend/backend-config.json | 14 ++ .../lib/nodejs/getParams.js | 3 +- .../function/getClientSecrets/amplify.state | 6 + .../getClientSecrets/custom-policies.json | 6 + .../getClientSecrets/function-parameters.json | 15 ++ ...ClientSecrets-cloudformation-template.json | 231 ++++++++++++++++++ .../function/getClientSecrets/src/index.js | 34 +++ .../getClientSecrets/src/package-lock.json | 23 ++ .../getClientSecrets/src/package.json | 11 + docker/files/amplify/amplify-meta.json.base | 5 + src/graphql/queries/clientSecrets.graphql | 7 + src/utils/safes/getContractUsefulMethods.ts | 38 +-- src/utils/safes/index.ts | 1 - 15 files changed, 363 insertions(+), 43 deletions(-) create mode 100644 amplify/backend/function/getClientSecrets/amplify.state create mode 100644 amplify/backend/function/getClientSecrets/custom-policies.json create mode 100644 amplify/backend/function/getClientSecrets/function-parameters.json create mode 100644 amplify/backend/function/getClientSecrets/getClientSecrets-cloudformation-template.json create mode 100644 amplify/backend/function/getClientSecrets/src/index.js create mode 100644 amplify/backend/function/getClientSecrets/src/package-lock.json create mode 100644 amplify/backend/function/getClientSecrets/src/package.json create mode 100644 src/graphql/queries/clientSecrets.graphql diff --git a/.env.example b/.env.example index 37eb2af2100..78a49a63441 100644 --- a/.env.example +++ b/.env.example @@ -14,10 +14,6 @@ SAFE_ENABLED="false" # DOES NOT NEED TO BE SET WHEN DEVELOPING LOCALLY NETWORK_CONTRACT_ADDRESS="0x0000000000000000000000000000000000000000" -# Required for safe control -BSCSCAN_API_KEY= -ETHERSCAN_API_KEY= - # The endpoint of the reputation oracle. REPUTATION_ORACLE_ENDPOINT="http://localhost:3001/reputation/local" diff --git a/amplify/backend/api/colonycdapp/schema/schema.graphql b/amplify/backend/api/colonycdapp/schema/schema.graphql index 1a8ab3871e9..52ae1c2cf88 100644 --- a/amplify/backend/api/colonycdapp/schema/schema.graphql +++ b/amplify/backend/api/colonycdapp/schema/schema.graphql @@ -1111,6 +1111,14 @@ type Query { """ getUserNotificationsHMAC: String @function(name: "getUserNotificationsHMAC-${env}") + getClientSecrets: ClientSecrets + @function(name: "getClientSecrets-${env}") +} + +type ClientSecrets { + pinataApiSecret: String + coinGeckoApiKey: String + arbiscanApiKey: String } """ diff --git a/amplify/backend/backend-config.json b/amplify/backend/backend-config.json index b03ccb9bcd8..b1b8a391b56 100644 --- a/amplify/backend/backend-config.json +++ b/amplify/backend/backend-config.json @@ -192,6 +192,20 @@ "providerPlugin": "awscloudformation", "service": "Lambda" }, + "getClientSecrets": { + "build": true, + "dependsOn": [ + { + "attributes": [ + "Arn" + ], + "category": "function", + "resourceName": "colonycdappSSMAccess" + } + ], + "providerPlugin": "awscloudformation", + "service": "Lambda" + }, "getUserNotificationsHMAC": { "build": true, "providerPlugin": "awscloudformation", diff --git a/amplify/backend/function/colonycdappSSMAccess/lib/nodejs/getParams.js b/amplify/backend/function/colonycdappSSMAccess/lib/nodejs/getParams.js index 16aa730e505..17ab498e631 100644 --- a/amplify/backend/function/colonycdappSSMAccess/lib/nodejs/getParams.js +++ b/amplify/backend/function/colonycdappSSMAccess/lib/nodejs/getParams.js @@ -12,7 +12,6 @@ const ParamNames = { appsyncApiKey: `%2Famplify%2Fcdapp%2F${ENV}%2FAWS_APPSYNC_API_KEY`, bnbRpcEndpoint: `%2Famplify%2Fcdapp%2F${ENV}%2FBNB_RPC_ENDPOINT`, ethRpcEndpoint: `%2Famplify%2Fcdapp%2F${ENV}%2FETH_RPC_ENDPOINT`, - bscscanApiKey: `%2Famplify%2Fcdapp%2F${ENV}%2FBSCSCAN_API_KEY`, etherscanApiKey: `%2Famplify%2Fcdapp%2F${ENV}%2FETHERSCAN_API_KEY`, bridgeXYZApiKey: `%2Famplify%2Fcdapp%2F${ENV}%2FBRIDGEXYZ_API_KEY`, bridgeXYZApiUrl: `%2Famplify%2Fcdapp%2F${ENV}%2FBRIDGEXYZ_API_URL`, @@ -21,6 +20,8 @@ const ParamNames = { magicbellApiSecret: `%2Famplify%2Fcdapp%2F${ENV}%2FMAGICBELL_API_SECRET`, coinGeckoApiUrl: `%2Famplify%2Fcdapp%2F${ENV}%2FCOINGECKO_API_URL`, coinGeckoApiKey: `%2Famplify%2Fcdapp%2F${ENV}%2FCOINGECKO_API_KEY`, + pinataApiSecret: `%2Famplify%2Fcdapp%2F${ENV}%2FPINATA_API_SECRET`, + arbiscanApiKey: `%2Famplify%2Fcdapp%2F${ENV}%2FARBISCAN_API_KEY`, }; const getParam = async (paramName) => { diff --git a/amplify/backend/function/getClientSecrets/amplify.state b/amplify/backend/function/getClientSecrets/amplify.state new file mode 100644 index 00000000000..ab8a6cc8046 --- /dev/null +++ b/amplify/backend/function/getClientSecrets/amplify.state @@ -0,0 +1,6 @@ +{ + "pluginId": "amplify-nodejs-function-runtime-provider", + "functionRuntime": "nodejs", + "useLegacyBuild": true, + "defaultEditorFile": "src/index.js" +} diff --git a/amplify/backend/function/getClientSecrets/custom-policies.json b/amplify/backend/function/getClientSecrets/custom-policies.json new file mode 100644 index 00000000000..1f21c530082 --- /dev/null +++ b/amplify/backend/function/getClientSecrets/custom-policies.json @@ -0,0 +1,6 @@ +[ + { + "Action": [], + "Resource": [] + } +] diff --git a/amplify/backend/function/getClientSecrets/function-parameters.json b/amplify/backend/function/getClientSecrets/function-parameters.json new file mode 100644 index 00000000000..d82236d2e51 --- /dev/null +++ b/amplify/backend/function/getClientSecrets/function-parameters.json @@ -0,0 +1,15 @@ +{ + "lambdaLayers": [ + { + "type": "ProjectLayer", + "resourceName": "colonycdappSSMAccess", + "env": "qa", + "version": "Always choose latest version", + "isLatestVersionSelected": true + }, + { + "type": "ExternalLayer", + "arn": "arn:aws:lambda:eu-west-2:133256977650:layer:AWS-Parameters-and-Secrets-Lambda-Extension:4" + } + ] +} diff --git a/amplify/backend/function/getClientSecrets/getClientSecrets-cloudformation-template.json b/amplify/backend/function/getClientSecrets/getClientSecrets-cloudformation-template.json new file mode 100644 index 00000000000..5aa32b92f0e --- /dev/null +++ b/amplify/backend/function/getClientSecrets/getClientSecrets-cloudformation-template.json @@ -0,0 +1,231 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "{\"createdOn\":\"Linux\",\"createdBy\":\"Amplify\",\"createdWith\":\"12.12.4\",\"stackType\":\"function-Lambda\",\"metadata\":{}}", + "Parameters": { + "CloudWatchRule": { + "Type": "String", + "Default": "NONE", + "Description": " Schedule Expression" + }, + "deploymentBucketName": { + "Type": "String" + }, + "env": { + "Type": "String" + }, + "s3Key": { + "Type": "String" + }, + "functioncolonycdappSSMAccessArn": { + "Type": "String", + "Default": "functioncolonycdappSSMAccessArn" + } + }, + "Conditions": { + "ShouldNotCreateEnvResources": { + "Fn::Equals": [ + { + "Ref": "env" + }, + "NONE" + ] + } + }, + "Resources": { + "LambdaFunction": { + "Type": "AWS::Lambda::Function", + "Metadata": { + "aws:asset:path": "./src", + "aws:asset:property": "Code" + }, + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "deploymentBucketName" + }, + "S3Key": { + "Ref": "s3Key" + } + }, + "Handler": "index.handler", + "FunctionName": { + "Fn::If": [ + "ShouldNotCreateEnvResources", + "getClientSecrets", + { + "Fn::Join": [ + "", + [ + "getClientSecrets", + "-", + { + "Ref": "env" + } + ] + ] + } + ] + }, + "Environment": { + "Variables": { + "ENV": { + "Ref": "env" + }, + "REGION": { + "Ref": "AWS::Region" + } + } + }, + "Role": { + "Fn::GetAtt": [ + "LambdaExecutionRole", + "Arn" + ] + }, + "Runtime": "nodejs20.x", + "Layers": [ + { + "Ref": "functioncolonycdappSSMAccessArn" + }, + "arn:aws:lambda:eu-west-2:133256977650:layer:AWS-Parameters-and-Secrets-Lambda-Extension:4" + ], + "Timeout": 60 + } + }, + "LambdaExecutionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "RoleName": { + "Fn::If": [ + "ShouldNotCreateEnvResources", + "colonycdappLambdaRoleGetClientSecrets", + { + "Fn::Join": [ + "", + [ + "colonycdappLambdaRoleGetClientSecrets", + "-", + { + "Ref": "env" + } + ] + ] + } + ] + }, + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + } + } + }, + "lambdaexecutionpolicy": { + "DependsOn": [ + "LambdaExecutionRole" + ], + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName": "lambda-execution-policy", + "Roles": [ + { + "Ref": "LambdaExecutionRole" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": { + "Fn::Sub": [ + "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*", + { + "region": { + "Ref": "AWS::Region" + }, + "account": { + "Ref": "AWS::AccountId" + }, + "lambda": { + "Ref": "LambdaFunction" + } + } + ] + } + }, + { + "Effect": "Allow", + "Action": [ + "ssm:GetParameter", + "kms:Decrypt" + ], + "Resource": { + "Fn::Sub": [ + "arn:aws:ssm:${region}:${account}:parameter/*", + { + "region": { + "Ref": "AWS::Region" + }, + "account": { + "Ref": "AWS::AccountId" + } + } + ] + } + } + ] + } + } + } + }, + "Outputs": { + "Name": { + "Value": { + "Ref": "LambdaFunction" + } + }, + "Arn": { + "Value": { + "Fn::GetAtt": [ + "LambdaFunction", + "Arn" + ] + } + }, + "Region": { + "Value": { + "Ref": "AWS::Region" + } + }, + "LambdaExecutionRole": { + "Value": { + "Ref": "LambdaExecutionRole" + } + }, + "LambdaExecutionRoleArn": { + "Value": { + "Fn::GetAtt": [ + "LambdaExecutionRole", + "Arn" + ] + } + } + } +} diff --git a/amplify/backend/function/getClientSecrets/src/index.js b/amplify/backend/function/getClientSecrets/src/index.js new file mode 100644 index 00000000000..1bdf1ed5504 --- /dev/null +++ b/amplify/backend/function/getClientSecrets/src/index.js @@ -0,0 +1,34 @@ +let pinataApiSecret = process.env.PINATA_API_SECRET; +let coinGeckoApiKey = process.env.COINGECKO_API_KEY; +let arbiscanApiKey = process.env.ARBISCAN_API_KEY; + +const setEnvVariables = async () => { + const ENV = process.env.ENV; + + if (ENV === 'qa' || ENV === 'prod') { + const { getParams } = require('/opt/nodejs/getParams'); + [pinataApiSecret, coinGeckoApiKey, arbiscanApiKey] = await getParams([ + 'pinataApiSecret', + 'coinGeckoApiKey', + 'arbiscanApiKey', + ]); + } +}; + +exports.handler = async (event) => { + try { + await setEnvVariables(); + } catch (err) { + throw new Error('Unable to set environment variables. Reason:', err); + } + + if (!event.request.headers['x-wallet-address']) { + return null; + } + + return { + pinataApiSecret, + coinGeckoApiKey, + arbiscanApiKey, + }; +}; diff --git a/amplify/backend/function/getClientSecrets/src/package-lock.json b/amplify/backend/function/getClientSecrets/src/package-lock.json new file mode 100644 index 00000000000..1b5e7e6316c --- /dev/null +++ b/amplify/backend/function/getClientSecrets/src/package-lock.json @@ -0,0 +1,23 @@ +{ + "name": "getclientsecrets", + "version": "2.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "getclientsecrets", + "version": "2.0.0", + "license": "Apache-2.0", + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.161", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.161.tgz", + "integrity": "sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/amplify/backend/function/getClientSecrets/src/package.json b/amplify/backend/function/getClientSecrets/src/package.json new file mode 100644 index 00000000000..5f24e86acb3 --- /dev/null +++ b/amplify/backend/function/getClientSecrets/src/package.json @@ -0,0 +1,11 @@ +{ + "name": "getclientsecrets", + "version": "2.0.0", + "description": "Lambda function generated by Amplify", + "main": "index.js", + "license": "Apache-2.0", + "devDependencies": { + "@types/aws-lambda": "^8.10.92" + }, + "dependencies": {} +} diff --git a/docker/files/amplify/amplify-meta.json.base b/docker/files/amplify/amplify-meta.json.base index d691429ccf5..dd366c33cff 100644 --- a/docker/files/amplify/amplify-meta.json.base +++ b/docker/files/amplify/amplify-meta.json.base @@ -120,6 +120,11 @@ "build": true, "providerPlugin": "awscloudformation", "service": "Lambda" + }, + "getClientSecrets": { + "build": true, + "providerPlugin": "awscloudformation", + "service": "Lambda" } } } \ No newline at end of file diff --git a/src/graphql/queries/clientSecrets.graphql b/src/graphql/queries/clientSecrets.graphql new file mode 100644 index 00000000000..bd8bad92059 --- /dev/null +++ b/src/graphql/queries/clientSecrets.graphql @@ -0,0 +1,7 @@ +query GetClientSecrets { + getClientSecrets { + pinataApiSecret + coinGeckoApiKey + arbiscanApiKey + } +} diff --git a/src/utils/safes/getContractUsefulMethods.ts b/src/utils/safes/getContractUsefulMethods.ts index 71ad3e223c4..2685bc43a34 100644 --- a/src/utils/safes/getContractUsefulMethods.ts +++ b/src/utils/safes/getContractUsefulMethods.ts @@ -1,10 +1,7 @@ import { type JsonFragment } from '@ethersproject/abi'; import { keccak256, toUtf8Bytes } from 'ethers/lib/utils'; -import { - ARBITRARY_TRANSACTION_NETWORKS, - BINANCE_NETWORK, -} from '~constants/index.ts'; +import { ARBITRARY_TRANSACTION_NETWORKS } from '~constants/index.ts'; export interface AbiItemExtended extends JsonFragment { name: string; @@ -20,39 +17,6 @@ const getCurrentNetworkData = (chainId: string) => { ); }; -export const getApiKey = (chainId: string) => { - if (chainId === BINANCE_NETWORK.chainId) { - return import.meta.env.BSCSCAN_API_KEY; - } - return import.meta.env.ETHERSCAN_API_KEY; -}; - -export const fetchContractName = async ( - contractAddress: string, - safeChainId: string, -): Promise => { - // will be defined since fetchContractName is only called if selectedSafe is defined - - const currentNetworkData = getCurrentNetworkData(safeChainId)!; - - const apiKey = getApiKey(currentNetworkData.chainId); - const apiUri = - `${currentNetworkData.apiUri}` + - `?apiKey=${apiKey}` + - `&module=contract` + - `&action=getsourcecode` + - `&address=${contractAddress}`; - - try { - const response = await fetch(apiUri); - const data = await response.json(); - return data.result[0].ContractName || ''; - } catch (error) { - console.error('Failed to get contract name', error); - return ''; - } -}; - export const fetchContractABI = async ( contractAddress: string, chainId: string, diff --git a/src/utils/safes/index.ts b/src/utils/safes/index.ts index 495e2c42912..4bc2ab5ad21 100644 --- a/src/utils/safes/index.ts +++ b/src/utils/safes/index.ts @@ -15,7 +15,6 @@ export { type AbiItemExtended, fetchContractABI, isAbiItem, - fetchContractName, } from './getContractUsefulMethods.ts'; export { getArrayFromString } from './contractParserValidation.ts'; From bca20b33a21f5afb3c82f46c385d3c34cafbbd79 Mon Sep 17 00:00:00 2001 From: Jakub Zajac Date: Thu, 26 Mar 2026 21:59:26 +0000 Subject: [PATCH 2/7] Try: get amplify to delete old API key --- amplify/backend/api/colonycdapp/parameters.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amplify/backend/api/colonycdapp/parameters.json b/amplify/backend/api/colonycdapp/parameters.json index e556e912db0..44dcd96aa74 100644 --- a/amplify/backend/api/colonycdapp/parameters.json +++ b/amplify/backend/api/colonycdapp/parameters.json @@ -3,5 +3,6 @@ "DynamoDBBillingMode": "PAY_PER_REQUEST", "DynamoDBEnableServerSideEncryption": false, "DynamoDBEnablePointInTimeRecovery": "true", - "APIKeyExpirationEpoch": 0 + "APIKeyExpirationEpoch": 0, + "CreateAPIKey": 0 } From 81407c5a7aa976bc8a1f15a575ace56df15d8d10 Mon Sep 17 00:00:00 2001 From: Jakub Zajac Date: Thu, 26 Mar 2026 22:08:35 +0000 Subject: [PATCH 3/7] Now recreate the API key --- amplify/backend/api/colonycdapp/parameters.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/amplify/backend/api/colonycdapp/parameters.json b/amplify/backend/api/colonycdapp/parameters.json index 44dcd96aa74..e556e912db0 100644 --- a/amplify/backend/api/colonycdapp/parameters.json +++ b/amplify/backend/api/colonycdapp/parameters.json @@ -3,6 +3,5 @@ "DynamoDBBillingMode": "PAY_PER_REQUEST", "DynamoDBEnableServerSideEncryption": false, "DynamoDBEnablePointInTimeRecovery": "true", - "APIKeyExpirationEpoch": 0, - "CreateAPIKey": 0 + "APIKeyExpirationEpoch": 0 } From b0fb395a433060e32c2df30a374bc01790caf650 Mon Sep 17 00:00:00 2001 From: Jakub Zajac Date: Fri, 27 Mar 2026 12:04:21 +0000 Subject: [PATCH 4/7] Disable automatic API key creation on amplify deployment --- amplify/backend/api/colonycdapp/parameters.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amplify/backend/api/colonycdapp/parameters.json b/amplify/backend/api/colonycdapp/parameters.json index e556e912db0..44dcd96aa74 100644 --- a/amplify/backend/api/colonycdapp/parameters.json +++ b/amplify/backend/api/colonycdapp/parameters.json @@ -3,5 +3,6 @@ "DynamoDBBillingMode": "PAY_PER_REQUEST", "DynamoDBEnableServerSideEncryption": false, "DynamoDBEnablePointInTimeRecovery": "true", - "APIKeyExpirationEpoch": 0 + "APIKeyExpirationEpoch": 0, + "CreateAPIKey": 0 } From 7a8b8b0e9f6623a30c82c6fbc2ba406aa0ba817c Mon Sep 17 00:00:00 2001 From: Jakub Zajac Date: Mon, 30 Mar 2026 17:23:52 +0100 Subject: [PATCH 5/7] Read client secrets from new lambda --- .env.example | 5 -- .../function/getClientSecrets/.env.example | 3 ++ src/context/ipfs/ipfsWithFallbackContext.ts | 14 +----- src/context/ipfs/pinata/Pinata.ts | 15 ++---- src/graphql/generated.ts | 49 +++++++++++++++++++ src/redux/sagas/setupUserContext.ts | 40 ++++++++++++++- src/utils/clientSecrets.ts | 17 +++++++ .../currency/tokenHistoricalPriceByName.ts | 3 +- src/utils/currency/tokenPriceByAddress.ts | 3 +- src/utils/currency/tokenPriceByName.ts | 3 +- src/utils/currency/tokenPriceDataByName.ts | 3 +- src/utils/safes/getContractUsefulMethods.ts | 3 +- 12 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 amplify/backend/function/getClientSecrets/.env.example create mode 100644 src/utils/clientSecrets.ts diff --git a/.env.example b/.env.example index 78a49a63441..d0316d9c8c1 100644 --- a/.env.example +++ b/.env.example @@ -31,10 +31,8 @@ GOOGLE_TAG_MANAGER_ID= # pinata connection detail from https://app.pinata.cloud/developers/api-keys # Only needed for production PINATA_API_KEY= -PINATA_API_SECRET= # Needed for currency conversion. For testing, see: https://support.coingecko.com/hc/en-us/articles/21880397454233 -COINGECKO_API_KEY= COINGECKO_API_URL= POSTHOG_KEY= POSTHOG_HOST= @@ -49,8 +47,5 @@ MAGICBELL_API_KEY= # Used to set a local key so that in development you only recieve notifications from your current dev env MAGICBELL_DEV_KEY= -# Needed for Arbitrary transaction feature -ARBISCAN_API_KEY= - # Needed so that we have a wallet provided by Dynamic.xyz DYNAMIC_ENV_ID= diff --git a/amplify/backend/function/getClientSecrets/.env.example b/amplify/backend/function/getClientSecrets/.env.example new file mode 100644 index 00000000000..0c9e3d59668 --- /dev/null +++ b/amplify/backend/function/getClientSecrets/.env.example @@ -0,0 +1,3 @@ +PINATA_API_SECRET= +COINGECKO_API_KEY= +ARBISCAN_API_KEY= diff --git a/src/context/ipfs/ipfsWithFallbackContext.ts b/src/context/ipfs/ipfsWithFallbackContext.ts index 2c63ec3efbf..69679f95ee2 100644 --- a/src/context/ipfs/ipfsWithFallbackContext.ts +++ b/src/context/ipfs/ipfsWithFallbackContext.ts @@ -1,18 +1,6 @@ import getIPFSWithFallback from './getIpfsWithFallback.ts'; import IPFSNode from './ipfsnode/index.ts'; -import pinataClient from './pinataClient.ts'; -const getIPFSContext = () => { - if ( - import.meta.env.NETWORK_ID === 'ganache' || - !(import.meta.env.PINATA_API_KEY && import.meta.env.PINATA_API_SECRET) - ) { - const ipfsNode = new IPFSNode(); - return getIPFSWithFallback(ipfsNode); - } - return getIPFSWithFallback(undefined, pinataClient); -}; - -const ipfsWithFallback = getIPFSContext(); +const ipfsWithFallback = getIPFSWithFallback(new IPFSNode()); export default ipfsWithFallback; diff --git a/src/context/ipfs/pinata/Pinata.ts b/src/context/ipfs/pinata/Pinata.ts index 448540374fc..4b5456ae69a 100644 --- a/src/context/ipfs/pinata/Pinata.ts +++ b/src/context/ipfs/pinata/Pinata.ts @@ -1,13 +1,9 @@ +import clientSecrets from '~utils/clientSecrets.ts'; + import { PINATA_ENDPOINT, JSON_MIME_TYPE } from './constants.ts'; class Pinata { - hasApiAccess: boolean; - - constructor() { - this.hasApiAccess = !!( - import.meta.env.PINATA_API_KEY && import.meta.env.PINATA_API_SECRET - ); - } + private readonly secrets = clientSecrets; /** * Return a JSON string to IPFS using the Pinata.cloud API @@ -18,7 +14,7 @@ class Pinata { typeof data !== 'string' ? JSON.stringify(data) : data; try { - if (!this.hasApiAccess) { + if (!(import.meta.env.PINATA_API_KEY && this.secrets.pinataApiSecret)) { throw new Error('Client does not have the correct Pinata API keys'); } /* @@ -34,12 +30,11 @@ class Pinata { /* * @NOTE This is because Pinata.cloud makes us supply their non-standard headers */ - // @ts-ignore headers: { // eslint-disable-next-line camelcase pinata_api_key: import.meta.env.PINATA_API_KEY, // eslint-disable-next-line camelcase - pinata_secret_api_key: import.meta.env.PINATA_API_SECRET, + pinata_secret_api_key: this.secrets.pinataApiSecret, 'Content-Type': JSON_MIME_TYPE, }, body: potentialJSONBlob, diff --git a/src/graphql/generated.ts b/src/graphql/generated.ts index 4d3bff2e791..0a73dff56e3 100644 --- a/src/graphql/generated.ts +++ b/src/graphql/generated.ts @@ -270,6 +270,13 @@ export type ChainMetadataInput = { transactionHash?: InputMaybe; }; +export type ClientSecrets = { + __typename?: 'ClientSecrets'; + arbiscanApiKey?: Maybe; + coinGeckoApiKey?: Maybe; + pinataApiSecret?: Maybe; +}; + export enum ClientType { CoinMachineClient = 'CoinMachineClient', ColonyClient = 'ColonyClient', @@ -6653,6 +6660,7 @@ export type Query = { getActionsByColony?: Maybe; getAnnotation?: Maybe; getCacheTotalBalance?: Maybe; + getClientSecrets?: Maybe; getColoniesByNativeTokenId?: Maybe; getColony?: Maybe; getColonyAction?: Maybe; @@ -10687,6 +10695,11 @@ export type GetGatewayFeeQueryVariables = Exact<{ [key: string]: never; }>; export type GetGatewayFeeQuery = { __typename?: 'Query', bridgeGetGatewayFee?: { __typename?: 'BridgeGatewayFeeReturn', transactionFeePercentage?: number | null, success?: boolean | null } | null }; +export type GetClientSecretsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetClientSecretsQuery = { __typename?: 'Query', getClientSecrets?: { __typename?: 'ClientSecrets', pinataApiSecret?: string | null, coinGeckoApiKey?: string | null, arbiscanApiKey?: string | null } | null }; + export type GetFullColonyByAddressQueryVariables = Exact<{ address: Scalars['ID']; }>; @@ -13780,6 +13793,42 @@ export function useGetGatewayFeeLazyQuery(baseOptions?: Apollo.LazyQueryHookOpti export type GetGatewayFeeQueryHookResult = ReturnType; export type GetGatewayFeeLazyQueryHookResult = ReturnType; export type GetGatewayFeeQueryResult = Apollo.QueryResult; +export const GetClientSecretsDocument = gql` + query GetClientSecrets { + getClientSecrets { + pinataApiSecret + coinGeckoApiKey + arbiscanApiKey + } +} + `; + +/** + * __useGetClientSecretsQuery__ + * + * To run a query within a React component, call `useGetClientSecretsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetClientSecretsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetClientSecretsQuery({ + * variables: { + * }, + * }); + */ +export function useGetClientSecretsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetClientSecretsDocument, options); + } +export function useGetClientSecretsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetClientSecretsDocument, options); + } +export type GetClientSecretsQueryHookResult = ReturnType; +export type GetClientSecretsLazyQueryHookResult = ReturnType; +export type GetClientSecretsQueryResult = Apollo.QueryResult; export const GetFullColonyByAddressDocument = gql` query GetFullColonyByAddress($address: ID!) { getColonyByAddress(id: $address) { diff --git a/src/redux/sagas/setupUserContext.ts b/src/redux/sagas/setupUserContext.ts index cc219ad0361..f710f867f83 100644 --- a/src/redux/sagas/setupUserContext.ts +++ b/src/redux/sagas/setupUserContext.ts @@ -1,10 +1,15 @@ +import { type ApolloQueryResult } from '@apollo/client'; import { all, call, fork, put } from 'redux-saga/effects'; // import AppLoadingState from '~context/appLoadingState'; import { authenticateWallet } from '~auth/index.ts'; -import { getContext, ContextModule } from '~context/index.ts'; +import { getContext, setContext, ContextModule } from '~context/index.ts'; +import getIPFSWithFallback from '~context/ipfs/getIpfsWithFallback.ts'; +import pinataClient from '~context/ipfs/pinataClient.ts'; +import { GetClientSecretsDocument, type GetClientSecretsQuery } from '~gql'; import { failPendingTransactions } from '~state/transactionState.ts'; +import { setClientSecrets } from '~utils/clientSecrets.ts'; import { ActionTypes } from '../actionTypes.ts'; import { type AllActions } from '../types/actions/index.ts'; @@ -46,10 +51,43 @@ function* setupContextDependentSagas() { ]); } +function* fetchAndApplyClientSecrets() { + try { + const apolloClient = getContext(ContextModule.ApolloClient); + const { data }: ApolloQueryResult = + yield apolloClient.query({ + query: GetClientSecretsDocument, + }); + + const secrets = data?.getClientSecrets; + if (!secrets) return; + + setClientSecrets({ + pinataApiSecret: secrets.pinataApiSecret ?? '', + coinGeckoApiKey: secrets.coinGeckoApiKey ?? '', + arbiscanApiKey: secrets.arbiscanApiKey ?? '', + }); + + if ( + import.meta.env.NETWORK_ID !== 'ganache' && + import.meta.env.PINATA_API_KEY && + secrets.pinataApiSecret + ) { + setContext( + ContextModule.IPFSWithFallback, + getIPFSWithFallback(undefined, pinataClient), + ); + } + } catch (error) { + console.error('Could not fetch client secrets:', error); + } +} + function* initializeFullWallet() { // We're forking the next one as we don't really need to wait for it yield call(getGasPrices); yield call(authenticateWallet); + yield call(fetchAndApplyClientSecrets); yield fork(failPendingTransactions); } diff --git a/src/utils/clientSecrets.ts b/src/utils/clientSecrets.ts new file mode 100644 index 00000000000..c2602d8b61e --- /dev/null +++ b/src/utils/clientSecrets.ts @@ -0,0 +1,17 @@ +interface ClientSecrets { + pinataApiSecret: string; + coinGeckoApiKey: string; + arbiscanApiKey: string; +} + +const clientSecrets: ClientSecrets = { + pinataApiSecret: '', + coinGeckoApiKey: '', + arbiscanApiKey: '', +}; + +export const setClientSecrets = (secrets: Partial) => { + Object.assign(clientSecrets, secrets); +}; + +export default clientSecrets; diff --git a/src/utils/currency/tokenHistoricalPriceByName.ts b/src/utils/currency/tokenHistoricalPriceByName.ts index ddd1e0bfba8..72e1b6b838f 100644 --- a/src/utils/currency/tokenHistoricalPriceByName.ts +++ b/src/utils/currency/tokenHistoricalPriceByName.ts @@ -1,6 +1,7 @@ import { format } from 'date-fns'; import { SupportedCurrencies } from '~gql'; +import clientSecrets from '~utils/clientSecrets.ts'; import { currencyApiConfig, coinGeckoMappings } from './config.ts'; import { @@ -23,7 +24,7 @@ const buildHistoricalTokenNameCoinGeckoURL = ( currencyApiConfig.endpoints.tokenHistoricalPriceByName; return buildAPIEndpoint(new URL(`${url}/${tokenName}/history`), { - [searchParams.api]: import.meta.env.COINGECKO_API_KEY ?? '', + [searchParams.api]: clientSecrets.coinGeckoApiKey, [searchParams.date]: getDateSearchParamValue(date), }); }; diff --git a/src/utils/currency/tokenPriceByAddress.ts b/src/utils/currency/tokenPriceByAddress.ts index ad82cf26b25..c26a26d178c 100644 --- a/src/utils/currency/tokenPriceByAddress.ts +++ b/src/utils/currency/tokenPriceByAddress.ts @@ -1,5 +1,6 @@ import { SupportedCurrencies } from '~gql'; import { Network } from '~types/network.ts'; +import clientSecrets from '~utils/clientSecrets.ts'; import debugLogging from '~utils/debug/debugLogging.ts'; import { currencyApiConfig, coinGeckoMappings } from './config.ts'; @@ -28,7 +29,7 @@ const buildTokenAddressCoinGeckoURL = ( return buildAPIEndpoint(new URL(`${url}/${chain}`), { [searchParams.from]: contractAddress, [searchParams.to]: denomination, - [searchParams.api]: import.meta.env.COINGECKO_API_KEY ?? '', + [searchParams.api]: clientSecrets.coinGeckoApiKey, }); }; diff --git a/src/utils/currency/tokenPriceByName.ts b/src/utils/currency/tokenPriceByName.ts index 7cff0b33913..8d624f5e4b3 100644 --- a/src/utils/currency/tokenPriceByName.ts +++ b/src/utils/currency/tokenPriceByName.ts @@ -1,4 +1,5 @@ import { SupportedCurrencies } from '~gql'; +import clientSecrets from '~utils/clientSecrets.ts'; import debugLogging from '~utils/debug/debugLogging.ts'; import { currencyApiConfig, coinGeckoMappings } from './config.ts'; @@ -23,7 +24,7 @@ const buildTokenNameCoinGeckoURL = ( return buildAPIEndpoint(new URL(`${url}/`), { [searchParams.from]: tokenName, [searchParams.to]: denomination, - [searchParams.api]: import.meta.env.COINGECKO_API_KEY ?? '', + [searchParams.api]: clientSecrets.coinGeckoApiKey, }); }; diff --git a/src/utils/currency/tokenPriceDataByName.ts b/src/utils/currency/tokenPriceDataByName.ts index b6b3faa576f..bed94af1837 100644 --- a/src/utils/currency/tokenPriceDataByName.ts +++ b/src/utils/currency/tokenPriceDataByName.ts @@ -1,6 +1,7 @@ import fromUnixTime from 'date-fns/fromUnixTime'; import { SupportedCurrencies } from '~gql'; +import clientSecrets from '~utils/clientSecrets.ts'; import debugLogging from '~utils/debug/debugLogging.ts'; import { currencyApiConfig, coinGeckoMappings } from './config.ts'; @@ -30,7 +31,7 @@ const buildTokenNameCoinGeckoURL = ( return buildAPIEndpoint(new URL(`${url}/`), { [searchParams.from]: tokenName, [searchParams.to]: denomination, - [searchParams.api]: import.meta.env.COINGECKO_API_KEY ?? '', + [searchParams.api]: clientSecrets.coinGeckoApiKey, [searchParams.includeLastUpdatedAt]: 'true', }); }; diff --git a/src/utils/safes/getContractUsefulMethods.ts b/src/utils/safes/getContractUsefulMethods.ts index 2685bc43a34..534f5506d34 100644 --- a/src/utils/safes/getContractUsefulMethods.ts +++ b/src/utils/safes/getContractUsefulMethods.ts @@ -2,6 +2,7 @@ import { type JsonFragment } from '@ethersproject/abi'; import { keccak256, toUtf8Bytes } from 'ethers/lib/utils'; import { ARBITRARY_TRANSACTION_NETWORKS } from '~constants/index.ts'; +import clientSecrets from '~utils/clientSecrets.ts'; export interface AbiItemExtended extends JsonFragment { name: string; @@ -30,7 +31,7 @@ export const fetchContractABI = async ( const currentNetworkData = getCurrentNetworkData(chainId)!; - const apiKey = import.meta.env.ARBISCAN_API_KEY; + const { arbiscanApiKey: apiKey } = clientSecrets; const apiUri = `${currentNetworkData.apiUri}` + `?apiKey=${apiKey}` + From 4c59917b8f11669e4abb5818c15d4e16a60580d7 Mon Sep 17 00:00:00 2001 From: Jakub Zajac Date: Mon, 30 Mar 2026 18:10:10 +0100 Subject: [PATCH 6/7] Set long expiration date for amplify API key --- amplify/backend/api/colonycdapp/parameters.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amplify/backend/api/colonycdapp/parameters.json b/amplify/backend/api/colonycdapp/parameters.json index 44dcd96aa74..b68fb256f55 100644 --- a/amplify/backend/api/colonycdapp/parameters.json +++ b/amplify/backend/api/colonycdapp/parameters.json @@ -3,6 +3,6 @@ "DynamoDBBillingMode": "PAY_PER_REQUEST", "DynamoDBEnableServerSideEncryption": false, "DynamoDBEnablePointInTimeRecovery": "true", - "APIKeyExpirationEpoch": 0, - "CreateAPIKey": 0 + "APIKeyExpirationEpoch": 1805968800, + "CreateAPIKey": 1 } From 41a354b3b6d24143a98ffec26752b3313f072c3e Mon Sep 17 00:00:00 2001 From: Jakub Zajac Date: Mon, 30 Mar 2026 18:18:09 +0100 Subject: [PATCH 7/7] Try not creating an API key via amplify at all --- amplify/backend/api/colonycdapp/parameters.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/amplify/backend/api/colonycdapp/parameters.json b/amplify/backend/api/colonycdapp/parameters.json index b68fb256f55..4dec79402a7 100644 --- a/amplify/backend/api/colonycdapp/parameters.json +++ b/amplify/backend/api/colonycdapp/parameters.json @@ -3,6 +3,5 @@ "DynamoDBBillingMode": "PAY_PER_REQUEST", "DynamoDBEnableServerSideEncryption": false, "DynamoDBEnablePointInTimeRecovery": "true", - "APIKeyExpirationEpoch": 1805968800, - "CreateAPIKey": 1 + "CreateAPIKey": 0 }