Skip to content

Commit f49d003

Browse files
feat: add Tron payment detection retrievers and advanced logic support (#1691)
## Description of the changes Added a new Substreams module for indexing ERC20FeeProxy payment events on the TRON blockchain. This module: - Tracks `TransferWithReferenceAndFee` events from deployed ERC20FeeProxy contracts on TRON mainnet and Nile testnet - Extracts payment details including token address, amount, recipient, payment reference, fee information, and transaction metadata - Provides a Rust implementation with WASM compilation for Substreams - Includes configuration for deployment as a Substreams-powered subgraph - Contains Makefile, build scripts, and package configuration for easy development and deployment - Implements Base58 encoding for TRON addresses - Supports both mainnet (`TCUDPYnS9dH3WvFEaE7wN7vnDa51J4R4fd`) and Nile testnet (`THK5rNmrvCujhmrXa5DB1dASepwXTr9cJs`) contract addresses --- Closes RequestNetwork/private-issues#226
1 parent 23fd8e3 commit f49d003

30 files changed

Lines changed: 1139 additions & 190 deletions

packages/advanced-logic/src/advanced-logic.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,16 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic
173173
}
174174

175175
public getFeeProxyContractErc20ForNetwork(network?: string): FeeProxyContractErc20 {
176-
return NearChains.isChainSupported(network)
177-
? new FeeProxyContractErc20(this.currencyManager, undefined, undefined, network)
178-
: this.extensions.feeProxyContractErc20;
176+
if (NearChains.isChainSupported(network) || this.isTronNetwork(network)) {
177+
return new FeeProxyContractErc20(this.currencyManager, undefined, undefined, network);
178+
}
179+
return this.extensions.feeProxyContractErc20;
180+
}
181+
182+
private isTronNetwork(network?: string): boolean {
183+
if (!network) return false;
184+
const normalized = network.toLowerCase();
185+
return normalized === 'tron' || normalized === 'nile';
179186
}
180187

181188
protected getNetwork(

packages/advanced-logic/src/extensions/payment-network/address-based.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,14 @@ export default abstract class AddressBasedPaymentNetwork<
158158
}
159159
}
160160

161+
/**
162+
* Check if an address is a valid TRON Base58 address (starts with 'T', 34 chars).
163+
* Use this in subclasses that override isValidAddress for TRON-specific networks.
164+
*/
165+
public static isTronAddress(address: string): boolean {
166+
return /^T[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address);
167+
}
168+
161169
protected isValidAddressForSymbolAndNetwork(
162170
address: string,
163171
symbol: string,

packages/advanced-logic/src/extensions/payment-network/erc20/fee-proxy-contract.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CurrencyTypes, ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types';
22
import { NearChains, isSameChain } from '@requestnetwork/currency';
3-
import { UnsupportedNetworkError } from '../address-based';
3+
import AddressBasedPaymentNetwork, { UnsupportedNetworkError } from '../address-based';
44
import { FeeReferenceBasedPaymentNetwork } from '../fee-reference-based';
55

66
const EVM_CURRENT_VERSION = '0.2.0';
@@ -57,8 +57,16 @@ export default class Erc20FeeProxyPaymentNetwork<
5757
} else {
5858
return this.isValidAddressForSymbolAndNetwork(address, 'NEAR', 'near');
5959
}
60+
} else if (Erc20FeeProxyPaymentNetwork.isTronNetwork(this.network)) {
61+
return AddressBasedPaymentNetwork.isTronAddress(address);
6062
} else {
6163
return super.isValidAddress(address);
6264
}
6365
}
66+
67+
private static isTronNetwork(network?: string): boolean {
68+
if (!network) return false;
69+
const normalized = network.toLowerCase();
70+
return normalized === 'tron' || normalized === 'nile';
71+
}
6472
}

packages/advanced-logic/test/extensions/payment-network/address-based.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,43 @@ describe('extensions/payment-network/address-based', () => {
4949
testAddressBasedPaymentNetwork.testIsValidAddress();
5050
}).toThrowError(new UnsupportedCurrencyError({ value: 'test', network: 'mainnet' }));
5151
});
52+
53+
it('base isValidAddress should reject TRON addresses for ERC20 currency type', () => {
54+
class TestAddressBasedPaymentNetwork extends AddressBasedPaymentNetwork {
55+
public constructor() {
56+
super(
57+
CurrencyManager.getDefault(),
58+
ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_ADDRESS_BASED,
59+
'test',
60+
RequestLogicTypes.CURRENCY.ERC20,
61+
);
62+
}
63+
public testIsValidAddress(address: string) {
64+
return this.isValidAddress(address);
65+
}
66+
}
67+
const pn = new TestAddressBasedPaymentNetwork();
68+
// A valid TRON Base58 address should NOT be accepted by the base class
69+
// because the base class has no network context to know this is a TRON request
70+
expect(pn.testIsValidAddress('TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW')).toBe(false);
71+
// A valid Ethereum address should still be accepted
72+
expect(pn.testIsValidAddress('0x0000000000000000000000000000000000000000')).toBe(true);
73+
});
74+
75+
it('static isTronAddress should correctly validate TRON addresses', () => {
76+
expect(AddressBasedPaymentNetwork.isTronAddress('TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW')).toBe(
77+
true,
78+
);
79+
expect(AddressBasedPaymentNetwork.isTronAddress('T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb')).toBe(
80+
true,
81+
);
82+
// Invalid: too short
83+
expect(AddressBasedPaymentNetwork.isTronAddress('TJCnKsPa7y5okkXvQAid')).toBe(false);
84+
// Invalid: doesn't start with T
85+
expect(
86+
AddressBasedPaymentNetwork.isTronAddress('0x0000000000000000000000000000000000000000'),
87+
).toBe(false);
88+
// Invalid: empty
89+
expect(AddressBasedPaymentNetwork.isTronAddress('')).toBe(false);
90+
});
5291
});

packages/currency/src/chains/declarative/data/nile.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

packages/currency/src/chains/declarative/data/tron.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

packages/payment-detection/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,99 @@ The code generation is included in the pre-build script and can be run manually:
6666
yarn codegen
6767
```
6868

69+
## TRON Payment Detection (Hasura-based)
70+
71+
TRON payment detection uses a Hasura GraphQL API backed by a PostgreSQL database that is populated by a Substreams-based indexer. This approach was chosen because The Graph does not support subgraphs for native TRON (only TRON EVM).
72+
73+
### Architecture
74+
75+
```
76+
TRON Blockchain → Substreams → PostgreSQL → Hasura GraphQL → SDK
77+
```
78+
79+
The payment data flows through:
80+
81+
1. **Substreams**: Indexes ERC20FeeProxy payment events from the TRON blockchain
82+
2. **PostgreSQL**: Stores payment data via `substreams-sink-sql`
83+
3. **Hasura**: Exposes the PostgreSQL data as a GraphQL API
84+
4. **SDK**: Queries Hasura via `TronInfoRetriever` and `HasuraClient`
85+
86+
### Components
87+
88+
- **`TronFeeProxyPaymentDetector`**: Payment detector for TRON ERC20 Fee Proxy payments
89+
- **`TronInfoRetriever`**: Retrieves payment events from Hasura, implements `ITheGraphBaseInfoRetriever`
90+
- **`HasuraClient`**: GraphQL client for querying the Hasura endpoint
91+
92+
### Usage
93+
94+
The `TronFeeProxyPaymentDetector` is automatically registered in the `PaymentNetworkFactory` for TRON networks (`tron` and `nile`).
95+
96+
```typescript
97+
import { PaymentNetworkFactory } from '@requestnetwork/payment-detection';
98+
99+
// The factory automatically uses TronFeeProxyPaymentDetector for TRON
100+
const paymentNetwork = PaymentNetworkFactory.getPaymentNetworkFromRequest({
101+
request,
102+
advancedLogic,
103+
});
104+
105+
const balance = await paymentNetwork.getBalance(request);
106+
```
107+
108+
### Custom Hasura Endpoint
109+
110+
By default, the `HasuraClient` connects to the production Hasura endpoint. To use a custom endpoint:
111+
112+
```typescript
113+
import {
114+
HasuraClient,
115+
TronInfoRetriever,
116+
TronFeeProxyPaymentDetector,
117+
} from '@requestnetwork/payment-detection';
118+
119+
// Create a custom Hasura client
120+
const customClient = new HasuraClient({
121+
baseUrl: 'https://your-hasura-instance.com/v1/graphql',
122+
});
123+
124+
// Use it with TronInfoRetriever
125+
const retriever = new TronInfoRetriever(customClient);
126+
127+
// Or use getHasuraClient with custom options
128+
import { getHasuraClient } from '@requestnetwork/payment-detection';
129+
130+
const client = getHasuraClient('tron', {
131+
baseUrl: 'https://your-hasura-instance.com/v1/graphql',
132+
});
133+
```
134+
135+
### TRON-specific Event Fields
136+
137+
TRON payment events include additional fields specific to the TRON blockchain:
138+
139+
```typescript
140+
interface TronPaymentEvent {
141+
txHash: string;
142+
feeAmount: string;
143+
block: number;
144+
to: string;
145+
from: string;
146+
feeAddress?: string;
147+
tokenAddress?: string;
148+
// TRON-specific resource consumption
149+
energyUsed?: string; // Total energy consumed
150+
energyFee?: string; // Energy fee in SUN
151+
netFee?: string; // Network/bandwidth fee in SUN
152+
}
153+
```
154+
155+
### Supported Networks
156+
157+
| Network | Chain Identifier | Description |
158+
| ------- | ---------------- | ----------------- |
159+
| `tron` | `tron` | TRON Mainnet |
160+
| `nile` | `tron-nile` | TRON Nile Testnet |
161+
69162
# Test
70163

71164
```sh

packages/payment-detection/codegen.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
overwrite: true
2-
schema: 'https://api.studio.thegraph.com/query/67444/request-payments-sepolia/version/latest'
2+
schema: 'https://api.studio.thegraph.com/query/67444/request-payments-base/version/latest'
33
documents: src/thegraph/queries/*.graphql
44
generates:
55
src/thegraph/generated/graphql.ts:

packages/payment-detection/src/erc20/fee-proxy-contract.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { makeGetDeploymentInformation } from '../utils';
1414
import { TheGraphClient, TheGraphInfoRetriever } from '../thegraph';
1515
import { ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types';
1616
import { NearInfoRetriever } from '../near';
17-
import { TronInfoRetriever } from '../tron/tron-info-retriever';
17+
import { TronTheGraphInfoRetriever } from '../tron/retrievers/tron-thegraph-info-retriever';
1818
import { NetworkNotSupported } from '../balance-error';
1919

2020
const PROXY_CONTRACT_ADDRESS_MAP = {
@@ -166,13 +166,13 @@ export class ERC20FeeProxyPaymentDetector<
166166
| TheGraphClient
167167
| TheGraphClient<CurrencyTypes.NearChainName>
168168
| TheGraphClient<CurrencyTypes.TronChainName>,
169-
): TheGraphInfoRetriever | NearInfoRetriever | TronInfoRetriever {
169+
): TheGraphInfoRetriever | NearInfoRetriever | TronTheGraphInfoRetriever {
170170
const graphInfoRetriever = EvmChains.isChainSupported(paymentChain)
171171
? new TheGraphInfoRetriever(subgraphClient as TheGraphClient, this.currencyManager)
172172
: NearChains.isChainSupported(paymentChain) && this.network
173173
? new NearInfoRetriever(subgraphClient as TheGraphClient<CurrencyTypes.NearChainName>)
174174
: TronChains.isChainSupported(paymentChain)
175-
? new TronInfoRetriever(subgraphClient as TheGraphClient<CurrencyTypes.TronChainName>)
175+
? new TronTheGraphInfoRetriever(subgraphClient as TheGraphClient<CurrencyTypes.TronChainName>)
176176
: undefined;
177177
if (!graphInfoRetriever) {
178178
throw new Error(

packages/payment-detection/src/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ import {
3131
unpadAmountFromChainlink,
3232
} from './utils';
3333
import { NearConversionNativeTokenPaymentDetector, NearNativeTokenPaymentDetector } from './near';
34-
import { TronERC20FeeProxyPaymentDetector, TronInfoRetriever } from './tron';
34+
import {
35+
TronFeeProxyPaymentDetector,
36+
getHasuraClient,
37+
HasuraClient,
38+
TronInfoRetriever,
39+
} from './tron';
40+
export type { TronPaymentEvent } from './tron/retrievers/tron-info-retriever';
3541
import { FeeReferenceBasedDetector } from './fee-reference-based-detector';
3642
import { SuperFluidPaymentDetector } from './erc777/superfluid-detector';
3743
import { EscrowERC20InfoRetriever } from './erc20/escrow-info-retriever';
@@ -57,8 +63,11 @@ export {
5763
SuperFluidPaymentDetector,
5864
NearNativeTokenPaymentDetector,
5965
NearConversionNativeTokenPaymentDetector,
60-
TronERC20FeeProxyPaymentDetector,
66+
// TRON
67+
TronFeeProxyPaymentDetector,
6168
TronInfoRetriever,
69+
getHasuraClient,
70+
HasuraClient,
6271
EscrowERC20InfoRetriever,
6372
SuperFluidInfoRetriever,
6473
MetaDetector,

0 commit comments

Comments
 (0)