Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
7 changes: 6 additions & 1 deletion .github/workflows/auto_assign_pr.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
name: 'Auto Assign'
on:
pull_request:
pull_request_target:
types: [opened, ready_for_review]
branches: [master]

permissions:
contents: read
pull-requests: write

jobs:
add-reviews:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types';
import { FeeReferenceBasedPaymentNetwork } from '../fee-reference-based';

const CURRENT_VERSION = '0.1.0';
const CURRENT_VERSION = '0.2.0';

/**
* Implementation of the payment network to pay in ERC20 based on a transferable receivable contract.
Expand Down
2 changes: 1 addition & 1 deletion packages/currency/src/chains/ChainsAbstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export abstract class ChainsAbstract<
/**
* Check if chainName lives amongst the list of supported chains by this chain type.
*/
public isChainSupported(chainName?: string): boolean {
public isChainSupported(chainName?: string): chainName is CHAIN_NAME {
return !!chainName && (this.chainNames as string[]).includes(chainName);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/ethereum-storage/src/ethereum-storage-ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,14 @@ export class EthereumStorageEthers implements StorageTypes.IStorageWrite {
ipfs: { size: ipfsSize },
local: { location: ipfsHash },
ethereum: {
nonce: tx.nonce,
transactionHash: tx.hash,
blockConfirmation: tx.confirmations,
blockNumber: Number(tx.blockNumber),
// wrong value, but this metadata will not be used, as it's in Pending state
blockTimestamp: -1,
networkName: this.network,
smartContractAddress: this.txSubmitter.hashSubmitterAddress,
transactionHash: tx.hash,
},
state: StorageTypes.ContentState.PENDING,
storageType: StorageTypes.StorageSystemType.LOCAL,
Expand Down
57 changes: 40 additions & 17 deletions packages/integration-test/test/node-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const provider = new providers.JsonRpcProvider('http://localhost:8545');
const wallet = Wallet.fromMnemonic(mnemonic).connect(provider);

// eslint-disable-next-line no-magic-numbers
jest.setTimeout(10000);
jest.setTimeout(30000);

const requestCreationHashBTC: Types.IRequestInfo = {
currency: 'BTC',
Expand Down Expand Up @@ -71,6 +71,29 @@ const wrongDecryptionProvider = new EthereumPrivateKeyDecryptionProvider({
method: Types.Encryption.METHOD.ECIES,
});

const waitForConfirmation = async (input: Request | Types.IRequestDataWithEvents) => {
// Create the promise to wait for confirmation.
const waitForConfirmationPromise = new Promise<Types.IRequestDataWithEvents>((resolve) =>
input.on('confirmed', resolve),
);

// In parallel, mine an empty block, because a confirmation needs to wait for two blocks
// (the block that persisted the action + another block).
const mineBlockPromise = provider.send('evm_mine', []);

// Set a time limit to wait for confirmation before throwing.
// Create the error object right away to conserve the context's stacktrace.
const timeoutError = new Error('waiting for confirmation took too long');
const timeout = setTimeout(() => {
throw timeoutError;
}, 5000);

// Return the confirmation result.
const promiseResults = await Promise.all([waitForConfirmationPromise, mineBlockPromise]);
clearTimeout(timeout);
return promiseResults[0];
};

describe('Request client using a request node', () => {
it('can create a request, change the amount and get data', async () => {
// Create a request
Expand All @@ -89,7 +112,7 @@ describe('Request client using a request node', () => {
expect(requestData.meta).toBeDefined();
expect(requestData.pending!.state).toBe(Types.RequestLogic.STATE.CREATED);

requestData = await request.waitForConfirmation();
requestData = await waitForConfirmation(request);
expect(requestData.state).toBe(Types.RequestLogic.STATE.CREATED);
expect(requestData.pending).toBeNull();

Expand All @@ -101,7 +124,7 @@ describe('Request client using a request node', () => {
expect(requestData.meta).toBeDefined();
expect(requestData.pending!.expectedAmount).toBe('800');

requestData = await new Promise((resolve) => requestData.on('confirmed', resolve));
requestData = await waitForConfirmation(requestData);
expect(requestData.expectedAmount).toBe('800');
expect(requestData.pending).toBeNull();
});
Expand Down Expand Up @@ -145,15 +168,15 @@ describe('Request client using a request node', () => {
expect(extension.events[0].name).toBe('create');
expect(extension.events[0].parameters).toEqual(paymentNetwork.parameters);

requestData = await request.waitForConfirmation();
requestData = await waitForConfirmation(request);
expect(requestData.state).toBe(Types.RequestLogic.STATE.CREATED);
expect(requestData.pending).toBeNull();

requestData = await request.declareSentPayment('100', 'bank transfer initiated', payerIdentity);
expect(requestData.balance).toBeDefined();
expect(requestData.balance!.balance).toBe('0');

requestData = await new Promise((resolve) => requestData.on('confirmed', resolve));
requestData = await waitForConfirmation(requestData);
expect(requestData.balance!.balance).toBe('0');

requestData = await request.declareReceivedPayment(
Expand All @@ -164,7 +187,7 @@ describe('Request client using a request node', () => {
expect(requestData.balance).toBeDefined();
expect(requestData.balance!.balance).toBe('0');

requestData = await new Promise((resolve) => requestData.on('confirmed', resolve));
requestData = await waitForConfirmation(requestData);
expect(requestData.balance!.balance).toBe('100');
});

Expand All @@ -187,7 +210,7 @@ describe('Request client using a request node', () => {
signer: payeeIdentity,
topics: topicsRequest1and2,
});
await request1.waitForConfirmation();
await waitForConfirmation(request1);
const timestampBeforeReduce = getCurrentTimestampInSecond();

// make sure that request 2 timestamp is greater than request 1 timestamp
Expand All @@ -209,15 +232,15 @@ describe('Request client using a request node', () => {
topics: topicsRequest1and2,
});

await request2.waitForConfirmation();
await waitForConfirmation(request2);

// reduce request 1
const requestDataReduce = await request1.reduceExpectedAmountRequest('10000000', payeeIdentity);
await new Promise((r) => requestDataReduce.on('confirmed', r));
await waitForConfirmation(requestDataReduce);

// cancel request 1
const requestDataCancel = await request1.cancel(payeeIdentity);
await new Promise((r) => requestDataCancel.on('confirmed', r));
await waitForConfirmation(requestDataCancel);

// get requests without boundaries
let requests = await requestNetwork.fromTopic(topicsRequest1and2[0]);
Expand Down Expand Up @@ -375,7 +398,7 @@ describe('Request client using a request node', () => {
expect(requestData.pending!.state).toBe(Types.RequestLogic.STATE.CREATED);
expect(requestData.meta!.transactionManagerMeta.encryptionMethod).toBe('ecies-aes256-gcm');

await new Promise((resolve) => request.on('confirmed', resolve));
await waitForConfirmation(request);

// Fetch the created request by its id
const fetchedRequest = await requestNetwork.fromRequestId(request.requestId);
Expand All @@ -395,7 +418,7 @@ describe('Request client using a request node', () => {
expect(fetchedRequestData.state).toBe(Types.RequestLogic.STATE.CREATED);

const acceptData = await request.accept(payerIdentity);
await new Promise((resolve) => acceptData.on('confirmed', resolve));
await waitForConfirmation(acceptData);

await fetchedRequest.refresh();
fetchedRequestData = fetchedRequest.getData();
Expand All @@ -405,7 +428,7 @@ describe('Request client using a request node', () => {
requestCreationHashBTC.expectedAmount,
payerIdentity,
);
await new Promise((resolve) => increaseData.on('confirmed', resolve));
await waitForConfirmation(increaseData);

await fetchedRequest.refresh();
expect(fetchedRequest.getData().expectedAmount).toEqual(
Expand All @@ -416,7 +439,7 @@ describe('Request client using a request node', () => {
Number(requestCreationHashBTC.expectedAmount) * 2,
payeeIdentity,
);
await new Promise((resolve) => reduceData.on('confirmed', resolve));
await waitForConfirmation(reduceData);

await fetchedRequest.refresh();
expect(fetchedRequest.getData().expectedAmount).toBe('0');
Expand All @@ -442,7 +465,7 @@ describe('Request client using a request node', () => {
},
[encryptionData.encryptionParams],
);
await encryptedRequest.waitForConfirmation();
await waitForConfirmation(encryptedRequest);

// Create a plain request
const plainRequest = await requestNetwork.createRequest({
Expand Down Expand Up @@ -533,12 +556,12 @@ describe('ERC20 localhost request creation and detection test', () => {
expect(requestData.meta).toBeDefined();
expect(requestData.pending!.state).toBe(Types.RequestLogic.STATE.CREATED);

requestData = await new Promise((resolve) => request.on('confirmed', resolve));
requestData = await waitForConfirmation(request);
expect(requestData.state).toBe(Types.RequestLogic.STATE.CREATED);
expect(requestData.pending).toBeNull();
});

it.only('can create ERC20 requests with any to erc20 proxy', async () => {
it('can create ERC20 requests with any to erc20 proxy', async () => {
const tokenContractAddress = '0x38cF23C52Bb4B13F051Aec09580a2dE845a7FA35';

const currencies: CurrencyInput[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ProxyERC20InfoRetriever from './proxy-info-retriever';

const ERC20_TRANSFERABLE_RECEIVABLE_CONTRACT_ADDRESS_MAP = {
['0.1.0']: '0.1.0',
['0.2.0']: '0.2.0',
};

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/payment-detection/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
formatAddress,
getPaymentNetworkExtension,
getPaymentReference,
hashReference,
padAmountForChainlink,
parseLogArgs,
unpadAmountFromChainlink,
Expand Down Expand Up @@ -59,5 +60,6 @@ export {
calculateEscrowState,
getPaymentNetworkExtension,
getPaymentReference,
hashReference,
formatAddress,
};
10 changes: 2 additions & 8 deletions packages/payment-detection/src/payment-network-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { EthFeeProxyPaymentDetector, EthInputDataPaymentDetector } from './eth';
import { AnyToERC20PaymentDetector, AnyToEthFeeProxyPaymentDetector } from './any';
import { NearConversionNativeTokenPaymentDetector, NearNativeTokenPaymentDetector } from './near';
import { getPaymentNetworkExtension } from './utils';
import { getTheGraphClient } from './thegraph';
import { defaultGetTheGraphClient } from './thegraph';
import { getDefaultProvider } from 'ethers';

const PN_ID = ExtensionTypes.PAYMENT_NETWORK_ID;
Expand Down Expand Up @@ -104,13 +104,7 @@ export class PaymentNetworkFactory {

private buildOptions(options: Partial<PaymentNetworkOptions>): PaymentNetworkOptions {
const defaultOptions: PaymentNetworkOptions = {
getSubgraphClient: (network) => {
return network === 'private'
? undefined
: getTheGraphClient(
`https://api.thegraph.com/subgraphs/name/requestnetwork/request-payments-${network}`,
);
},
getSubgraphClient: defaultGetTheGraphClient,
explorerApiKeys: {},
getRpcProvider: getDefaultProvider,
};
Expand Down
12 changes: 12 additions & 0 deletions packages/payment-detection/src/thegraph/client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { CurrencyTypes } from '@requestnetwork/types';
import { NearChains } from '@requestnetwork/currency';
import { GraphQLClient } from 'graphql-request';
import { getSdk } from './generated/graphql';
import { getSdk as getNearSdk } from './generated/graphql-near';

const HOSTED_THE_GRAPH_URL =
'https://api.thegraph.com/subgraphs/name/requestnetwork/request-payments-';

// NB: the GraphQL client is automatically generated based on files present in ./queries,
// using graphql-codegen.
// To generate types, run `yarn codegen`, then open the generated files so that the code editor picks up the changes.
Expand All @@ -26,3 +30,11 @@ export const getTheGraphClient = (url: string, options?: TheGraphClientOptions)

export const getTheGraphNearClient = (url: string, options?: TheGraphClientOptions) =>
getNearSdk(new GraphQLClient(url, options));

export const defaultGetTheGraphClient = (network: CurrencyTypes.ChainName) => {
return network === 'private'
? undefined
: NearChains.isChainSupported(network)
? getTheGraphNearClient(`${HOSTED_THE_GRAPH_URL}${network.replace('aurora', 'near')}`)
: getTheGraphClient(`${HOSTED_THE_GRAPH_URL}${network}`);
};
12 changes: 8 additions & 4 deletions packages/payment-detection/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ export const makeGetDeploymentInformation = <
};
};

export const hashReference = (paymentReference: string): string => {
return keccak256(`0x${paymentReference}`);
};

/**
* Returns escrow status based on array of escrow events
* @param escrowEvents Balance of the request being updated
Expand Down Expand Up @@ -177,6 +173,14 @@ export function getPaymentReference(
return PaymentReferenceCalculator.calculate(requestId, salt, info);
}

/**
* Returns the hash of a payment reference.
* @see getPaymentReference
*/
export const hashReference = (paymentReference: string): string => {
return keccak256(`0x${paymentReference}`);
};

/**
* For EVMs: alias to ethers.utils.getAddress that adds the key to error message, and supports nullish values.
* For other chains: applies lower-case to the address.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PaymentReferenceCalculator from '../../src/payment-reference-calculator';
import { utils } from 'ethers';
import { PaymentTypes } from '@requestnetwork/types';
import { CurrencyManager } from '@requestnetwork/currency';
import { hashReference } from '../../src';

const paymentsMockData = {
['0xc6e23a20c0a1933acc8e30247b5d1e2215796c1f' as string]: [
Expand Down Expand Up @@ -82,7 +83,7 @@ describe('api/erc20/thegraph-info-retriever', () => {
paymentData.salt,
paymentData.to,
);
const onChainReference = utils.keccak256(`0x${paymentReference}`);
const onChainReference = hashReference(paymentReference);
expect(onChainReference).toEqual(paymentData.reference);

const graphRetriever = new TheGraphInfoRetriever(clientMock, CurrencyManager.getDefault());
Expand Down
40 changes: 40 additions & 0 deletions packages/payment-detection/test/near/near-native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,46 @@ describe('Near payments detection', () => {
expect(balance.balance).toBe('1000000000000000000000000');
});

it('NearNativeTokenPaymentDetector can detect a payment on Near with an additional declarative payment', async () => {
const paymentDetector = new NearNativeTokenPaymentDetector({
network: 'aurora',
advancedLogic: advancedLogic,
currencyManager: CurrencyManager.getDefault(),
getSubgraphClient: mockedGetSubgraphClient,
});
const declarativeRequest = {
...request,
extensions: {
[ExtensionTypes.PAYMENT_NETWORK_ID.NATIVE_TOKEN as string]: {
id: ExtensionTypes.PAYMENT_NETWORK_ID.NATIVE_TOKEN,
type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
values: {
paymentAddress,
salt,
},
version: '0.2.0',
events: [
{
name: ExtensionTypes.PnAnyDeclarative.ACTION.DECLARE_RECEIVED_PAYMENT,
parameters: {
amount: '1000000000000000000000000',
note: 'first payment',
txHash: 'the-first-hash',
network: 'aurora',
},
timestamp: 10,
},
],
},
},
};
const balance = await paymentDetector.getBalance(declarativeRequest);

expect(mockedGetSubgraphClient).toHaveBeenCalled();
expect(balance.events).toHaveLength(2);
expect(balance.balance).toBe('2000000000000000000000000');
});

describe('Edge cases for NearNativeTokenPaymentDetector', () => {
it('throws with a wrong version', async () => {
let requestWithWrongVersion = deepCopy(request);
Expand Down
1 change: 1 addition & 0 deletions packages/payment-processor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './payment/erc777-utils';
export * from './payment/eth-input-data';
export * from './payment/near-input-data';
export * from './payment/near-conversion';
export * from './payment/near-fungible';
export * from './payment/eth-proxy';
export * from './payment/eth-fee-proxy';
export * from './payment/batch-proxy';
Expand Down
Loading