Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8e70500
feat: add Tron substreams indexing package
rodrigopavezi Jan 26, 2026
88f1995
fix: correct event signature hash and add validation in substreams
rodrigopavezi Jan 27, 2026
eb8ec74
fix: address PR review comments
rodrigopavezi Jan 27, 2026
be58fde
refactor: use params instead of hardcoded addresses in substreams
rodrigopavezi Jan 27, 2026
065c477
fix: address remaining PR review comments
rodrigopavezi Jan 27, 2026
b95b479
fix: address substreams review comments
rodrigopavezi Jan 27, 2026
8a649e3
feat: add CircleCI job for building substreams-tron Rust package
rodrigopavezi Jan 27, 2026
16c7e7d
fix: use pre-generated proto types instead of missing substreams-tron…
rodrigopavezi Jan 27, 2026
7af2f2a
fix: update Rust version to 1.85 for edition 2024 support
rodrigopavezi Jan 27, 2026
16edb59
fix: downgrade prost to 0.11 to match substreams dependency
rodrigopavezi Jan 27, 2026
1172353
fix: update package.json to comply with npmPkgJsonLint rules
rodrigopavezi Jan 27, 2026
7ed38e1
fix: skip Rust tests in Node.js test environment
rodrigopavezi Jan 27, 2026
6e9d00f
fix: correct import path in trc20-fee-proxy test
rodrigopavezi Jan 28, 2026
e9e81e3
refactor: move substreams-tron to payments-substream repo
rodrigopavezi Jan 29, 2026
3d04d08
fix: update codegen schema URL from sepolia to base
rodrigopavezi Jan 29, 2026
d7f1299
Merge: resolve conflicts in TRON payment detection
rodrigopavezi Feb 5, 2026
66b3bff
feat(payment-detection): add Hasura client for TRON payment detection
rodrigopavezi Feb 5, 2026
38bb634
resolve merge conflicts in tron retrievers
rodrigopavezi Feb 10, 2026
6d49d11
refactor(payment-detection): remove TronInfoRetriever and update Hasu…
rodrigopavezi Feb 10, 2026
4524cda
feat(payment-detection): add TRON support to fee proxy and enhance Ha…
rodrigopavezi Feb 10, 2026
a035fd2
fix(payment-detection): update import path for TronTheGraphInfoRetriever
rodrigopavezi Feb 10, 2026
89226a9
feat(payment-network): add support for TRON Base58 addresses in addre…
rodrigopavezi Feb 10, 2026
2897e53
feat(payment-network): enhance TRON address validation and support in…
rodrigopavezi Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/advanced-logic/src/advanced-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,16 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic
}

public getFeeProxyContractErc20ForNetwork(network?: string): FeeProxyContractErc20 {
return NearChains.isChainSupported(network)
? new FeeProxyContractErc20(this.currencyManager, undefined, undefined, network)
: this.extensions.feeProxyContractErc20;
if (NearChains.isChainSupported(network) || this.isTronNetwork(network)) {
return new FeeProxyContractErc20(this.currencyManager, undefined, undefined, network);
}
return this.extensions.feeProxyContractErc20;
}

private isTronNetwork(network?: string): boolean {
if (!network) return false;
const normalized = network.toLowerCase();
return normalized === 'tron' || normalized === 'nile';
}

protected getNetwork(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ export default abstract class AddressBasedPaymentNetwork<
}
}

/**
* Check if an address is a valid TRON Base58 address (starts with 'T', 34 chars).
* Use this in subclasses that override isValidAddress for TRON-specific networks.
*/
public static isTronAddress(address: string): boolean {
return /^T[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address);
}

protected isValidAddressForSymbolAndNetwork(
address: string,
symbol: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CurrencyTypes, ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types';
import { NearChains, isSameChain } from '@requestnetwork/currency';
import { UnsupportedNetworkError } from '../address-based';
import AddressBasedPaymentNetwork, { UnsupportedNetworkError } from '../address-based';
import { FeeReferenceBasedPaymentNetwork } from '../fee-reference-based';

const EVM_CURRENT_VERSION = '0.2.0';
Expand Down Expand Up @@ -57,8 +57,16 @@ export default class Erc20FeeProxyPaymentNetwork<
} else {
return this.isValidAddressForSymbolAndNetwork(address, 'NEAR', 'near');
}
} else if (Erc20FeeProxyPaymentNetwork.isTronNetwork(this.network)) {
return AddressBasedPaymentNetwork.isTronAddress(address);
} else {
return super.isValidAddress(address);
}
}

private static isTronNetwork(network?: string): boolean {
if (!network) return false;
const normalized = network.toLowerCase();
return normalized === 'tron' || normalized === 'nile';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,43 @@ describe('extensions/payment-network/address-based', () => {
testAddressBasedPaymentNetwork.testIsValidAddress();
}).toThrowError(new UnsupportedCurrencyError({ value: 'test', network: 'mainnet' }));
});

it('base isValidAddress should reject TRON addresses for ERC20 currency type', () => {
class TestAddressBasedPaymentNetwork extends AddressBasedPaymentNetwork {
public constructor() {
super(
CurrencyManager.getDefault(),
ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_ADDRESS_BASED,
'test',
RequestLogicTypes.CURRENCY.ERC20,
);
}
public testIsValidAddress(address: string) {
return this.isValidAddress(address);
}
}
const pn = new TestAddressBasedPaymentNetwork();
// A valid TRON Base58 address should NOT be accepted by the base class
// because the base class has no network context to know this is a TRON request
expect(pn.testIsValidAddress('TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW')).toBe(false);
// A valid Ethereum address should still be accepted
expect(pn.testIsValidAddress('0x0000000000000000000000000000000000000000')).toBe(true);
});

it('static isTronAddress should correctly validate TRON addresses', () => {
expect(AddressBasedPaymentNetwork.isTronAddress('TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW')).toBe(
true,
);
expect(AddressBasedPaymentNetwork.isTronAddress('T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb')).toBe(
true,
);
// Invalid: too short
expect(AddressBasedPaymentNetwork.isTronAddress('TJCnKsPa7y5okkXvQAid')).toBe(false);
// Invalid: doesn't start with T
expect(
AddressBasedPaymentNetwork.isTronAddress('0x0000000000000000000000000000000000000000'),
).toBe(false);
// Invalid: empty
expect(AddressBasedPaymentNetwork.isTronAddress('')).toBe(false);
});
});
10 changes: 0 additions & 10 deletions packages/currency/src/chains/declarative/data/nile.ts

This file was deleted.

20 changes: 0 additions & 20 deletions packages/currency/src/chains/declarative/data/tron.ts

This file was deleted.

93 changes: 93 additions & 0 deletions packages/payment-detection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,99 @@ The code generation is included in the pre-build script and can be run manually:
yarn codegen
```

## TRON Payment Detection (Hasura-based)

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).

### Architecture

```
TRON Blockchain → Substreams → PostgreSQL → Hasura GraphQL → SDK
```

The payment data flows through:

1. **Substreams**: Indexes ERC20FeeProxy payment events from the TRON blockchain
2. **PostgreSQL**: Stores payment data via `substreams-sink-sql`
3. **Hasura**: Exposes the PostgreSQL data as a GraphQL API
4. **SDK**: Queries Hasura via `TronInfoRetriever` and `HasuraClient`

### Components

- **`TronFeeProxyPaymentDetector`**: Payment detector for TRON ERC20 Fee Proxy payments
- **`TronInfoRetriever`**: Retrieves payment events from Hasura, implements `ITheGraphBaseInfoRetriever`
- **`HasuraClient`**: GraphQL client for querying the Hasura endpoint

### Usage

The `TronFeeProxyPaymentDetector` is automatically registered in the `PaymentNetworkFactory` for TRON networks (`tron` and `nile`).

```typescript
import { PaymentNetworkFactory } from '@requestnetwork/payment-detection';

// The factory automatically uses TronFeeProxyPaymentDetector for TRON
const paymentNetwork = PaymentNetworkFactory.getPaymentNetworkFromRequest({
request,
advancedLogic,
});

const balance = await paymentNetwork.getBalance(request);
```

### Custom Hasura Endpoint

By default, the `HasuraClient` connects to the production Hasura endpoint. To use a custom endpoint:

```typescript
import {
HasuraClient,
TronInfoRetriever,
TronFeeProxyPaymentDetector,
} from '@requestnetwork/payment-detection';

// Create a custom Hasura client
const customClient = new HasuraClient({
baseUrl: 'https://your-hasura-instance.com/v1/graphql',
});

// Use it with TronInfoRetriever
const retriever = new TronInfoRetriever(customClient);

// Or use getHasuraClient with custom options
import { getHasuraClient } from '@requestnetwork/payment-detection';

const client = getHasuraClient('tron', {
baseUrl: 'https://your-hasura-instance.com/v1/graphql',
});
```

### TRON-specific Event Fields

TRON payment events include additional fields specific to the TRON blockchain:

```typescript
interface TronPaymentEvent {
txHash: string;
feeAmount: string;
block: number;
to: string;
from: string;
feeAddress?: string;
tokenAddress?: string;
// TRON-specific resource consumption
energyUsed?: string; // Total energy consumed
energyFee?: string; // Energy fee in SUN
netFee?: string; // Network/bandwidth fee in SUN
}
```

### Supported Networks

| Network | Chain Identifier | Description |
| ------- | ---------------- | ----------------- |
| `tron` | `tron` | TRON Mainnet |
| `nile` | `tron-nile` | TRON Nile Testnet |

# Test

```sh
Expand Down
2 changes: 1 addition & 1 deletion packages/payment-detection/codegen.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
overwrite: true
schema: 'https://api.studio.thegraph.com/query/67444/request-payments-sepolia/version/latest'
schema: 'https://api.studio.thegraph.com/query/67444/request-payments-base/version/latest'
documents: src/thegraph/queries/*.graphql
generates:
src/thegraph/generated/graphql.ts:
Expand Down
7 changes: 5 additions & 2 deletions packages/payment-detection/src/erc20/fee-proxy-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
PaymentTypes,
RequestLogicTypes,
} from '@requestnetwork/types';
import { EvmChains, NearChains, isSameChain } from '@requestnetwork/currency';
import { EvmChains, NearChains, TronChains, isSameChain } from '@requestnetwork/currency';
import ProxyInfoRetriever from './proxy-info-retriever';

import { loadCurrencyFromContract } from './currency';
Expand All @@ -14,6 +14,7 @@ import { makeGetDeploymentInformation } from '../utils';
import { TheGraphClient, TheGraphInfoRetriever } from '../thegraph';
import { ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types';
import { NearInfoRetriever } from '../near';
import { TronTheGraphInfoRetriever } from '../tron/retrievers/tron-thegraph-info-retriever';
import { NetworkNotSupported } from '../balance-error';

const PROXY_CONTRACT_ADDRESS_MAP = {
Expand Down Expand Up @@ -165,11 +166,13 @@ export class ERC20FeeProxyPaymentDetector<
| TheGraphClient
| TheGraphClient<CurrencyTypes.NearChainName>
| TheGraphClient<CurrencyTypes.TronChainName>,
): TheGraphInfoRetriever | NearInfoRetriever {
): TheGraphInfoRetriever | NearInfoRetriever | TronTheGraphInfoRetriever {
const graphInfoRetriever = EvmChains.isChainSupported(paymentChain)
? new TheGraphInfoRetriever(subgraphClient as TheGraphClient, this.currencyManager)
: NearChains.isChainSupported(paymentChain) && this.network
? new NearInfoRetriever(subgraphClient as TheGraphClient<CurrencyTypes.NearChainName>)
: TronChains.isChainSupported(paymentChain)
? new TronTheGraphInfoRetriever(subgraphClient as TheGraphClient<CurrencyTypes.TronChainName>)
: undefined;
if (!graphInfoRetriever) {
throw new Error(
Expand Down
13 changes: 11 additions & 2 deletions packages/payment-detection/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ import {
unpadAmountFromChainlink,
} from './utils';
import { NearConversionNativeTokenPaymentDetector, NearNativeTokenPaymentDetector } from './near';
import { TronERC20FeeProxyPaymentDetector, TronInfoRetriever } from './tron';
import {
TronFeeProxyPaymentDetector,
getHasuraClient,
HasuraClient,
TronInfoRetriever,
} from './tron';
export type { TronPaymentEvent } from './tron/retrievers/tron-info-retriever';
import { FeeReferenceBasedDetector } from './fee-reference-based-detector';
import { SuperFluidPaymentDetector } from './erc777/superfluid-detector';
import { EscrowERC20InfoRetriever } from './erc20/escrow-info-retriever';
Expand All @@ -57,8 +63,11 @@ export {
SuperFluidPaymentDetector,
NearNativeTokenPaymentDetector,
NearConversionNativeTokenPaymentDetector,
TronERC20FeeProxyPaymentDetector,
// TRON
TronFeeProxyPaymentDetector,
TronInfoRetriever,
getHasuraClient,
HasuraClient,
EscrowERC20InfoRetriever,
SuperFluidInfoRetriever,
MetaDetector,
Expand Down
8 changes: 4 additions & 4 deletions packages/payment-detection/src/payment-network-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { SuperFluidPaymentDetector } from './erc777/superfluid-detector';
import { EthFeeProxyPaymentDetector, EthInputDataPaymentDetector } from './eth';
import { AnyToERC20PaymentDetector, AnyToEthFeeProxyPaymentDetector } from './any';
import { NearConversionNativeTokenPaymentDetector, NearNativeTokenPaymentDetector } from './near';
import { TronERC20FeeProxyPaymentDetector } from './tron';
import { TronFeeProxyPaymentDetector } from './tron';
import { getPaymentNetworkExtension } from './utils';
import { getTheGraphClient } from './thegraph';
import { getDefaultProvider } from 'ethers';
Expand Down Expand Up @@ -56,12 +56,12 @@ const supportedPaymentNetwork: ISupportedPaymentNetworkByCurrency = {
'near-testnet': {
[PN_ID.ERC20_FEE_PROXY_CONTRACT]: ERC20FeeProxyPaymentDetector<CurrencyTypes.NearChainName>,
},
// TRON chains
// TRON networks
tron: {
[PN_ID.ERC20_FEE_PROXY_CONTRACT]: TronERC20FeeProxyPaymentDetector,
[PN_ID.ERC20_FEE_PROXY_CONTRACT]: TronFeeProxyPaymentDetector,
},
nile: {
[PN_ID.ERC20_FEE_PROXY_CONTRACT]: TronERC20FeeProxyPaymentDetector,
[PN_ID.ERC20_FEE_PROXY_CONTRACT]: TronFeeProxyPaymentDetector,
},

'*': {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
schema: https://api.studio.thegraph.com/query/67444/request-payments-sepolia/version/latest
schema: https://api.studio.thegraph.com/query/67444/request-payments-base/version/latest
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Using local schema until the subgraph is deployed to The Graph Studio
schema: ../../../../../substreams-tron/schema.graphql
# Local schema for TRON payment queries
schema: ./schema.graphql
9 changes: 6 additions & 3 deletions packages/payment-detection/src/tron/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export { TronERC20FeeProxyPaymentDetector } from './tron-fee-proxy-detector';
export { TronInfoRetriever } from './tron-info-retriever';
export type { TronPaymentEvent } from './tron-info-retriever';
export { TronERC20FeeProxyPaymentDetector as TronFeeProxyPaymentDetector } from './tron-fee-proxy-detector';
export { TronInfoRetriever } from './retrievers/tron-info-retriever';
export type { TronPaymentEvent } from './retrievers/tron-info-retriever';
export { TronTheGraphInfoRetriever } from './retrievers/tron-thegraph-info-retriever';
export type { TronTheGraphPaymentEvent } from './retrievers/tron-thegraph-info-retriever';
export { HasuraClient, getHasuraClient } from './retrievers/hasura-client';
Loading