From 9f7039d5fcca1c8279c8b82a1d7b6bbbd1eeaa27 Mon Sep 17 00:00:00 2001 From: Tom Wilson Date: Thu, 5 Sep 2024 21:10:17 +0000 Subject: [PATCH 1/3] feat: matic support --- src/constants.ts | 4 + src/gateway/gateway.ts | 1 + src/gateway/index.ts | 1 + src/gateway/matic.ts | 93 ++++++++++++++++++++++++ src/jobs/creditPendingTx.ts | 2 + src/pricing/oracles/tokenToFiatOracle.ts | 2 + src/pricing/pricing.ts | 6 ++ src/server.ts | 2 + src/utils/base64.ts | 7 ++ 9 files changed, 118 insertions(+) create mode 100644 src/gateway/matic.ts diff --git a/src/constants.ts b/src/constants.ts index 266ab8b..9fa88a5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -331,6 +331,10 @@ export const ethereumGatewayUrl = new URL( process.env.ETHEREUM_GATEWAY || "https://cloudflare-eth.com/" ); +export const maticGatewayUrl = new URL( + process.env.MATIC_GATEWAY || "https://polygon-mainnet.infura.io/" +); + export const solanaGatewayUrl = new URL( process.env.SOLANA_GATEWAY || "https://api.mainnet-beta.solana.com/" ); diff --git a/src/gateway/gateway.ts b/src/gateway/gateway.ts index 489b405..345b31d 100644 --- a/src/gateway/gateway.ts +++ b/src/gateway/gateway.ts @@ -29,6 +29,7 @@ export const supportedPaymentTokens = [ "ethereum", "solana", "kyve", + "matic" ] as const; export type TokenType = (typeof supportedPaymentTokens)[number]; export function isSupportedPaymentToken(token: string): token is TokenType { diff --git a/src/gateway/index.ts b/src/gateway/index.ts index 19f8385..367353c 100644 --- a/src/gateway/index.ts +++ b/src/gateway/index.ts @@ -20,3 +20,4 @@ export * from "./kyve"; export * from "./solana"; export * from "./ethereum"; export * from "./arweave"; +export * from './matic'; diff --git a/src/gateway/matic.ts b/src/gateway/matic.ts new file mode 100644 index 0000000..25aeb74 --- /dev/null +++ b/src/gateway/matic.ts @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import BigNumber from "bignumber.js"; +import { ethers } from "ethers"; + +import { maticGatewayUrl } from "../constants"; +import { PaymentTransactionNotFound } from "../database/errors"; +import logger from "../logger"; +import { TransactionId } from "../types"; +import { + Gateway, + GatewayParams, + TransactionInfo, + TransactionStatus, +} from "./gateway"; + +type MaticGatewayParams = GatewayParams; + +export class MaticGateway extends Gateway { + public endpoint: URL; + private provider: ethers.JsonRpcProvider; + + constructor({ + endpoint = maticGatewayUrl, + paymentTxPollingWaitTimeMs, + pendingTxMaxAttempts, + minConfirmations = +(process.env.MATIC_MIN_CONFIRMATIONS || 5), + }: MaticGatewayParams = {}) { + super({ + paymentTxPollingWaitTimeMs, + pendingTxMaxAttempts, + minConfirmations, + }); + this.endpoint = endpoint; + this.provider = new ethers.JsonRpcProvider(endpoint.toString()); + } + + public async getTransactionStatus( + transactionId: TransactionId + ): Promise { + logger.debug("Getting transaction status...", { transactionId }); + const statusResponse = await this.provider.getTransactionReceipt( + transactionId + ); + if (statusResponse === null) { + logger.debug("Transaction not found...", { transactionId }); + return { status: "not found" }; + } + + if ((await statusResponse.confirmations()) >= this.minConfirmations) { + return { + status: "confirmed", + blockHeight: statusResponse.blockNumber, + }; + } + + return { status: "pending" }; + } + + public async getTransaction( + transactionId: TransactionId + ): Promise { + return this.pollGatewayForTx(async () => { + logger.debug("Getting transaction...", { transactionId }); + const txResponse = await this.provider.getTransaction(transactionId); + if (txResponse === null) { + throw new PaymentTransactionNotFound(transactionId); + } + + const tx = { + transactionQuantity: BigNumber(txResponse.value.toString()), + transactionSenderAddress: txResponse.from, + transactionRecipientAddress: txResponse.to ?? "", + }; + + return tx; + }, transactionId); + } +} diff --git a/src/jobs/creditPendingTx.ts b/src/jobs/creditPendingTx.ts index 3381de4..1140466 100644 --- a/src/jobs/creditPendingTx.ts +++ b/src/jobs/creditPendingTx.ts @@ -21,6 +21,7 @@ import { EthereumGateway, KyveGateway, SolanaGateway, + MaticGateway, } from "../gateway"; import globalLogger from "../logger"; @@ -39,6 +40,7 @@ export async function creditPendingTransactionsHandler({ ethereum: new EthereumGateway(), solana: new SolanaGateway(), kyve: new KyveGateway(), + matic: new MaticGateway() }, paymentDatabase = new PostgresDatabase(), logger = globalLogger.child({ job: "credit-pending-transactions-job" }), diff --git a/src/pricing/oracles/tokenToFiatOracle.ts b/src/pricing/oracles/tokenToFiatOracle.ts index 08fd09e..76694f9 100644 --- a/src/pricing/oracles/tokenToFiatOracle.ts +++ b/src/pricing/oracles/tokenToFiatOracle.ts @@ -32,6 +32,7 @@ const coinGeckoTokenNames = [ "ethereum", "solana", "kyve-network", + "matic-network" ] as const; type CoinGeckoTokenName = (typeof coinGeckoTokenNames)[number]; @@ -41,6 +42,7 @@ const tokenNameToCoinGeckoTokenName: Record = { ethereum: "ethereum", solana: "solana", kyve: "kyve-network", + matic: "matic-network" }; type CoinGeckoResponse = Record< diff --git a/src/pricing/pricing.ts b/src/pricing/pricing.ts index fc52085..0bdf395 100644 --- a/src/pricing/pricing.ts +++ b/src/pricing/pricing.ts @@ -792,6 +792,10 @@ export function weiToEth(wei: BigNumber.Value): BigNumber { return BigNumber(wei).shiftedBy(-18); } +export function baseToMatic(base: BigNumber.Value): BigNumber { + return BigNumber(base).shiftedBy(-18); +} + export function lamportsToSol(lamports: BigNumber.Value): BigNumber { return BigNumber(lamports).shiftedBy(-9); } @@ -813,6 +817,8 @@ export function baseAmountToTokenAmount( return lamportsToSol(amount); case "kyve": return ukyveToKyve(amount); + case "matic": + return baseToMatic(amount); default: return BigNumber(amount); } diff --git a/src/server.ts b/src/server.ts index 8a7c4a5..fe9a7ec 100644 --- a/src/server.ts +++ b/src/server.ts @@ -35,6 +35,7 @@ import { GatewayMap, KyveGateway, SolanaGateway, + MaticGateway } from "./gateway"; import logger from "./logger"; import { MetricRegistry } from "./metricRegistry"; @@ -99,6 +100,7 @@ export async function createServer( ethereum: new EthereumGateway(), solana: new SolanaGateway(), kyve: new KyveGateway(), + matic: new MaticGateway() }; const emailProvider = (() => { diff --git a/src/utils/base64.ts b/src/utils/base64.ts index 510e288..363e4a1 100644 --- a/src/utils/base64.ts +++ b/src/utils/base64.ts @@ -62,6 +62,11 @@ export function isValidEthAddress(address: string) { return ethAddressRegex.test(address); } +export function isValidMaticAddress(address: string) { + const maticAddressRegex = new RegExp("^0x[a-fA-F0-9]{40}$"); + return maticAddressRegex.test(address); +} + export function isValidKyveAddress(address: string) { const kyveAddressRegex = new RegExp("^kyve[a-zA-Z0-9]{39}$"); return kyveAddressRegex.test(address); @@ -80,6 +85,8 @@ export function isValidUserAddress( return isValidEthAddress(address); case "kyve": return isValidKyveAddress(address); + case "matic": + return isValidMaticAddress(address); default: return false; } From b46626a460b43fc478c778250074f3e8b36025b6 Mon Sep 17 00:00:00 2001 From: Tom Wilson Date: Thu, 5 Sep 2024 22:05:02 +0000 Subject: [PATCH 2/3] added tests for matic --- tests/helpers/stubs.ts | 13 +++++++++++++ tests/helpers/testHelpers.ts | 4 ++++ tests/router.int.test.ts | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/helpers/stubs.ts b/tests/helpers/stubs.ts index 82b8b56..b3bcff0 100644 --- a/tests/helpers/stubs.ts +++ b/tests/helpers/stubs.ts @@ -346,6 +346,19 @@ export const expectedTokenPrices = { hkd: 0.181921, brl: 0.126463, }, + "matic-network": { + usd: 0.368571, + jpy: 52.83, + eur: 0.331564, + gbp: 0.279491, + inr: 30.93, + aud: 0.546444, + sgd: 0.478965, + cad: 0.497505, + hkd: 2.87, + brl: 2.05, + } +} }; // TODO: we could make this a function and apply it against the arweave rates above using the turboPercentageFee constant diff --git a/tests/helpers/testHelpers.ts b/tests/helpers/testHelpers.ts index e2ac340..c93fe36 100644 --- a/tests/helpers/testHelpers.ts +++ b/tests/helpers/testHelpers.ts @@ -27,6 +27,7 @@ import { EthereumGateway, KyveGateway, SolanaGateway, + MaticGateway, } from "../../src/gateway"; import { ArweaveBytesToWinstonOracle, @@ -150,6 +151,9 @@ export const gatewayMap = { kyve: new KyveGateway({ paymentTxPollingWaitTimeMs: 0, }), + matic: new MaticGateway({ + paymentTxPollingWaitTimeMs: 0, + }) }; export const testAddress = "-kYy3_LcYeKhtqNNXDN6xTQ7hW8S5EV0jgq_6j8a830"; // cspell:disable-line diff --git a/tests/router.int.test.ts b/tests/router.int.test.ts index a78a525..b932d41 100644 --- a/tests/router.int.test.ts +++ b/tests/router.int.test.ts @@ -2197,7 +2197,7 @@ describe("Router tests", () => { expect(data).to.deep.equal({ winc: "1000000000", balance: "1000000000" }); }); - const tokens = ["arweave", "ethereum", "solana", "kyve"] as const; + const tokens = ["arweave", "ethereum", "solana", "kyve", "matic"] as const; for (const token of tokens) { it(`GET /account/balance/${token} returns 200 for valid params`, async () => { From 3844c0fd200bb68b7eaccb83abceadc47a6d5995 Mon Sep 17 00:00:00 2001 From: Tom Wilson Date: Thu, 5 Sep 2024 22:34:00 +0000 Subject: [PATCH 3/3] fix: added matic to dbTypes --- src/database/dbTypes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database/dbTypes.ts b/src/database/dbTypes.ts index f614e8d..907a505 100644 --- a/src/database/dbTypes.ts +++ b/src/database/dbTypes.ts @@ -51,6 +51,7 @@ export const userAddressTypes = [ "solana", "ethereum", "kyve", + "matic" ] as const; export type UserAddressType = (typeof userAddressTypes)[number];