Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b887c34
refactor: update ERC20CommerceEscrowWrapper deployment script to supp…
rodrigopavezi Dec 9, 2025
e5c1743
Update packages/smart-contracts/src/lib/artifacts/AuthCaptureEscrow/i…
rodrigopavezi Dec 9, 2025
ebb9c68
chore: update TheGraph API URLs and clean up client logic
rodrigopavezi Dec 9, 2025
3216bf7
Merge branch 'feat/commerce-escrow-eth-sepolia-deployment' of https:/…
rodrigopavezi Dec 9, 2025
6ab211b
test: enhance request-client.js tests with real timers and payment op…
rodrigopavezi Dec 10, 2025
8e6d598
test: update request-client.js tests to use advanced fake timers
rodrigopavezi Dec 10, 2025
f9a4cbc
test: refactor request-client.js tests to use jest.spyOn for mocking …
rodrigopavezi Dec 10, 2025
885bf17
test: improve request-client.js tests with mock server resets and tim…
rodrigopavezi Dec 10, 2025
04617bf
test: ensure proper mock restoration in request-client.js tests
rodrigopavezi Dec 11, 2025
ad93c7e
test: simplify timer management in request-client.js tests
rodrigopavezi Dec 11, 2025
0d7798b
test: replace jest fake timers with setTimeout in request-client.js t…
rodrigopavezi Dec 11, 2025
8b72a4b
test: enhance request-client.js tests with improved readability and s…
rodrigopavezi Dec 11, 2025
6394b8f
test: extend timeout for request-client.js tests to improve reliability
rodrigopavezi Dec 11, 2025
e5c3f3a
test: update erc20-commerce-escrow-wrapper tests to use real wrapper …
rodrigopavezi Dec 11, 2025
4079433
refactor: prepare ERC20CommerceEscrowWrapper for Create2 deployment (…
MantisClone Dec 11, 2025
bfe6cfe
feat: add ERC20CommerceEscrowWrapper deployed addresses for Sepolia a…
MantisClone Dec 12, 2025
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
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://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-sepolia/api'
schema: 'https://api.studio.thegraph.com/query/67444/request-payments-sepolia/version/latest'
documents: src/thegraph/queries/*.graphql
generates:
src/thegraph/generated/graphql.ts:
Expand Down
24 changes: 1 addition & 23 deletions packages/payment-detection/src/thegraph/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ const THE_GRAPH_STUDIO_URL =
const THE_GRAPH_EXPLORER_URL =
'https://gateway.thegraph.com/api/$API_KEY/subgraphs/id/$SUBGRAPH_ID';

const THE_GRAPH_ALCHEMY_URL =
'https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-$NETWORK/api';

const THE_GRAPH_URL_MANTLE_TESTNET =
'https://graph.testnet.mantle.xyz/subgraphs/name/requestnetwork/request-payments-mantle-testnet';

Expand All @@ -23,19 +20,6 @@ const THE_GRAPH_URL_MANTLE =
const THE_GRAPH_URL_CORE =
'https://thegraph.coredao.org/subgraphs/name/requestnetwork/request-payments-core';

const THE_GRAPH_ALCHEMY_CHAINS: CurrencyTypes.ChainName[] = [
'arbitrum-one',
'avalanche',
'base',
'bsc',
'fantom',
'mainnet',
'matic',
'sepolia',
'optimism',
'zksyncera',
];

const THE_GRAPH_EXPLORER_SUBGRAPH_ID: Partial<Record<CurrencyTypes.ChainName, string>> = {
['arbitrum-one']: '3MtDdHbzvBVNBpzUTYXGuDDLgTd1b8bPYwoH1Hdssgp9',
avalanche: 'A27V4PeZdKHeyuBkehdBJN8cxNtzVpXvYoqkjHUHRCFp',
Expand Down Expand Up @@ -159,10 +143,8 @@ export const getTheGraphClientUrl = (
'$API_KEY',
theGraphExplorerApiKey || '',
).replace('$SUBGRAPH_ID', theGraphExplorerSubgraphId || '');
const theGraphAlchemyUrl = THE_GRAPH_ALCHEMY_URL.replace('$NETWORK', chain);

const shouldUseTheGraphExplorer = !!theGraphExplorerApiKey && !!theGraphExplorerSubgraphId;
const shouldUseAlchemy = THE_GRAPH_ALCHEMY_CHAINS.includes(chain);

switch (true) {
case chain === 'private':
Expand All @@ -174,10 +156,6 @@ export const getTheGraphClientUrl = (
case chain === 'core':
return THE_GRAPH_URL_CORE;
default:
return shouldUseTheGraphExplorer
? theGraphExplorerUrl
: shouldUseAlchemy
? theGraphAlchemyUrl
: theGraphStudioUrl;
return shouldUseTheGraphExplorer ? theGraphExplorerUrl : theGraphStudioUrl;
}
};
4 changes: 2 additions & 2 deletions packages/payment-detection/test/thegraph/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ describe('getTheGraphClientUrl', () => {
const url = getTheGraphClientUrl('base', { url: 'test' });
expect(url).toBe('test');
});
it('should build the correct URL for network supported by Alchemy', () => {
it('should build the correct URL for network using TheGraph Studio', () => {
const url = getTheGraphClientUrl('base');
expect(url).toBe(
'https://subgraph.satsuma-prod.com/e2e4905ab7c8/request-network--434873/request-payments-base/api',
'https://api.studio.thegraph.com/query/67444/request-payments-base/version/latest',
);
});
it('should build the correct URL when using TheGraph Explorer API key', () => {
Expand Down
55 changes: 34 additions & 21 deletions packages/request-client.js/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const waitForConfirmation = async (
/* eslint-disable @typescript-eslint/no-unused-expressions */
describe('request-client.js', () => {
afterEach(() => {
jest.useRealTimers();
jest.resetAllMocks();
});

Expand Down Expand Up @@ -204,10 +205,12 @@ describe('request-client.js', () => {
mockServer.listen({ onUnhandledRequest: 'bypass' });
});
beforeEach(() => {
mockServer.resetHandlers();
spyPersistTransaction.mockReturnValue({});
spyGetTransactionsByChannelId.mockReturnValue({ result: mockedTransactions });
});
afterAll(() => {
mockServer.resetHandlers();
mockServer.close();
});

Expand Down Expand Up @@ -354,6 +357,7 @@ describe('request-client.js', () => {
mockServer.close();
});
beforeEach(() => {
mockServer.resetHandlers();
hits = { get: 0, post: 0 };
});
it('allows to create a request', async () => {
Expand Down Expand Up @@ -1156,6 +1160,7 @@ describe('request-client.js', () => {

describe('ETH requests', () => {
beforeEach(() => {
jest.useRealTimers();
jest.clearAllMocks();
jest.restoreAllMocks();
});
Expand Down Expand Up @@ -1280,17 +1285,19 @@ describe('request-client.js', () => {

// This test checks that 2 payments with reference `c19da4923539c37f` have reached 0xc12F17Da12cd01a9CDBB216949BA0b41A6Ffc4EB
it('can get the balance of an ETH request', async () => {
jest.useFakeTimers({ advanceTimers: true });
const etherscanMock = new EtherscanProviderMock();
ethers.providers.EtherscanProvider.prototype.getHistory = jest
.fn()
.mockImplementation(etherscanMock.getHistory);
ethers.providers.EtherscanProvider.prototype.getNetwork = jest
.fn()
.mockImplementation(etherscanMock.getNetwork);
jest
.spyOn(ethers.providers.EtherscanProvider.prototype, 'getHistory')
.mockImplementation(etherscanMock.getHistory.bind(etherscanMock));
jest
.spyOn(ethers.providers.EtherscanProvider.prototype, 'getNetwork')
.mockImplementation(etherscanMock.getNetwork.bind(etherscanMock));

const requestNetwork = new RequestNetwork({
signatureProvider: TestData.fakeSignatureProvider,
useMockStorage: true,
paymentOptions: { getSubgraphClient: () => undefined },
});

const paymentNetwork: PaymentTypes.PaymentNetworkCreateParameters = {
Expand All @@ -1311,12 +1318,13 @@ describe('request-client.js', () => {
});

const request = await requestNetwork.createRequest({
disablePaymentDetection: true,
paymentNetwork,
requestInfo,
signer: TestData.payee.identity,
});
await request.waitForConfirmation();

jest.advanceTimersByTime(150);
const data = await request.refresh();

// Payment reference should be fixed
Expand All @@ -1328,6 +1336,8 @@ describe('request-client.js', () => {
),
).toBe('efce79375b2db9f7');

request.enablePaymentDetection();
jest.advanceTimersByTime(150);
const dataAfterRefresh = await request.refresh();

expect(dataAfterRefresh.balance?.balance).toBe('12300000000');
Expand All @@ -1338,22 +1348,24 @@ describe('request-client.js', () => {
expect(dataAfterRefresh.balance?.events[0].parameters!.txHash).toBe(
'0x06d95c3889dcd974106e82fa27358549d9392d6fee6ea14fe1acedadc1013114',
);
jest.useRealTimers();
});

it('can disable and enable the get the balance of a request', async () => {
jest.useFakeTimers();
jest.useFakeTimers({ advanceTimers: true });

const etherscanMock = new EtherscanProviderMock();
ethers.providers.EtherscanProvider.prototype.getHistory = jest
.fn()
.mockImplementation(etherscanMock.getHistory);
ethers.providers.EtherscanProvider.prototype.getNetwork = jest
.fn()
.mockImplementation(etherscanMock.getNetwork);
jest
.spyOn(ethers.providers.EtherscanProvider.prototype, 'getHistory')
.mockImplementation(etherscanMock.getHistory.bind(etherscanMock));
jest
.spyOn(ethers.providers.EtherscanProvider.prototype, 'getNetwork')
.mockImplementation(etherscanMock.getNetwork.bind(etherscanMock));

const requestNetwork = new RequestNetwork({
signatureProvider: TestData.fakeSignatureProvider,
useMockStorage: true,
paymentOptions: { getSubgraphClient: () => undefined },
});

const paymentNetwork: PaymentTypes.PaymentNetworkCreateParameters = {
Expand Down Expand Up @@ -1425,19 +1437,20 @@ describe('request-client.js', () => {
});

it('can get the balance on a skipped payment detection request', async () => {
jest.useFakeTimers();
jest.useFakeTimers({ advanceTimers: true });

const etherscanMock = new EtherscanProviderMock();
ethers.providers.EtherscanProvider.prototype.getHistory = jest
.fn()
.mockImplementation(etherscanMock.getHistory);
ethers.providers.EtherscanProvider.prototype.getNetwork = jest
.fn()
.mockImplementation(etherscanMock.getNetwork);
jest
.spyOn(ethers.providers.EtherscanProvider.prototype, 'getHistory')
.mockImplementation(etherscanMock.getHistory.bind(etherscanMock));
jest
.spyOn(ethers.providers.EtherscanProvider.prototype, 'getNetwork')
.mockImplementation(etherscanMock.getNetwork.bind(etherscanMock));

const requestNetwork = new RequestNetwork({
signatureProvider: TestData.fakeSignatureProvider,
useMockStorage: true,
paymentOptions: { getSubgraphClient: () => undefined },
});

const paymentNetwork: PaymentTypes.PaymentNetworkCreateParameters = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { deployOne } from './deploy-one';

// Base Mainnet & Base Sepolia Contract Addresses
const BASE_SEPOLIA_CONTRACTS = {
AuthCaptureEscrow: '0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff',
ERC3009PaymentCollector: '0x0E3dF9510de65469C4518D7843919c0b8C7A7757',
Permit2PaymentCollector: '0x992476B9Ee81d52a5BdA0622C333938D0Af0aB26',
PreApprovalPaymentCollector: '0x1b77ABd71FCD21fbe2398AE821Aa27D1E6B94bC6',
SpendPermissionPaymentCollector: '0x8d9F34934dc9619e5DC3Df27D0A40b4A744E7eAa',
OperatorRefundCollector: '0x934907bffd0901b6A21e398B9C53A4A38F02fa5d',
// Contract Addresses by Network
const NETWORK_CONTRACTS: Record<
string,
{
AuthCaptureEscrow: string;
ERC20FeeProxy?: string;
}
> = {
'base-sepolia': {
AuthCaptureEscrow: '0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff',
},
sepolia: {
AuthCaptureEscrow: '0xF81E3F293c92CaCfc0d723d2D8183e39Cc3AEdC7',
ERC20FeeProxy: '0x399F5EE127ce7432E4921a61b8CF52b0af52cbfE',
},
};

/**
* Deploy ERC20CommerceEscrowWrapper using official Base contracts
* Deploy ERC20CommerceEscrowWrapper using network-specific contracts
*
* This script will:
* 1. Deploy ERC20FeeProxy if not already deployed
* 2. Use the official AuthCaptureEscrow contract deployed on Base Sepolia
* 1. Use existing ERC20FeeProxy if available, or deploy a new one
* 2. Use the official AuthCaptureEscrow contract deployed on the target network
* 3. Deploy ERC20CommerceEscrowWrapper with the above dependencies
*/
export default async function deployERC20CommerceEscrowWrapper(
Expand All @@ -42,16 +49,34 @@ export default async function deployERC20CommerceEscrowWrapper(
console.log(`Deployer: ${deployer.address}`);
console.log(`Deployer balance: ${hre.ethers.utils.formatEther(await deployer.getBalance())} ETH`);

// Step 1: Deploy ERC20FeeProxy
console.log('\n--- Step 1: Deploying ERC20FeeProxy ---');
const { address: erc20FeeProxyAddress } = await deployOne(args, hre, 'ERC20FeeProxy', {
verify: true,
});
console.log(`✅ ERC20FeeProxy deployed at: ${erc20FeeProxyAddress}`);
// Get network-specific contract addresses
const networkContracts = NETWORK_CONTRACTS[hre.network.name];
if (!networkContracts) {
throw new Error(
`Network ${hre.network.name} is not configured. Please add contract addresses to NETWORK_CONTRACTS.`,
);
}

let erc20FeeProxyAddress: string;

// Step 1: Get or Deploy ERC20FeeProxy
console.log('\n--- Step 1: ERC20FeeProxy ---');
if (networkContracts.ERC20FeeProxy) {
// Use existing ERC20FeeProxy
erc20FeeProxyAddress = networkContracts.ERC20FeeProxy;
console.log(`✅ Using existing ERC20FeeProxy at: ${erc20FeeProxyAddress}`);
} else {
// Deploy ERC20FeeProxy
const result = await deployOne(args, hre, 'ERC20FeeProxy', {
verify: true,
});
erc20FeeProxyAddress = result.address;
console.log(`✅ ERC20FeeProxy deployed at: ${erc20FeeProxyAddress}`);
}

// Step 2: Use official AuthCaptureEscrow contract
console.log('\n--- Step 2: Using official AuthCaptureEscrow ---');
const authCaptureEscrowAddress = BASE_SEPOLIA_CONTRACTS.AuthCaptureEscrow;
const authCaptureEscrowAddress = networkContracts.AuthCaptureEscrow;
console.log(`✅ Using official AuthCaptureEscrow at: ${authCaptureEscrowAddress}`);

// Step 3: Deploy ERC20CommerceEscrowWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export const authCaptureEscrowArtifact = new ContractArtifact<AuthCaptureEscrow>
address: '0x0000000000000000000000000000000000000000',
creationBlockNumber: 0,
},
// Base Sepolia deployment (same address as mainnet via CREATE2)
// Sepolia deployment
sepolia: {
address: '0x1234567890123456789012345678901234567890', // Placeholder - to be updated with actual deployment
creationBlockNumber: 0,
address: '0xF81E3F293c92CaCfc0d723d2D8183e39Cc3AEdC7',
creationBlockNumber: 9795220,
},
// Base Mainnet deployment (same address as sepolia via CREATE2)
// Base Mainnet deployment
base: {
address: '0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff',
creationBlockNumber: 29931650,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,13 @@ export const erc20CommerceEscrowWrapperArtifact = new ContractArtifact<ERC20Comm
},
// Testnet deployments for testing
sepolia: {
address: '0x1234567890123456789012345678901234567890', // Placeholder - to be updated with actual deployment
creationBlockNumber: 0,
},
goerli: {
address: '0x2345678901234567890123456789012345678901', // Placeholder - to be updated with actual deployment
creationBlockNumber: 0,
},
mumbai: {
address: '0x3456789012345678901234567890123456789012', // Placeholder - to be updated with actual deployment
address: '0x4062C5c38b14A90f9293010a72b53FA3b400bD46',
creationBlockNumber: 0,
},
'base-sepolia': {
address: '0xDF4945F8AB31C666714f34DDF8Ac9445379fD3f5', // To be updated after deployment
address: '0xDF4945F8AB31C666714f34DDF8Ac9445379fD3f5',
creationBlockNumber: 0,
},
// TODO: Add deployment addresses for mainnet networks once deployed
// mainnet: {
// address: '0x0000000000000000000000000000000000000000',
// creationBlockNumber: 0,
// },
// matic: {
// address: '0x0000000000000000000000000000000000000000',
// creationBlockNumber: 0,
// },
},
},
},
Expand Down