Request Network Docs
WebsiteGithubStatusDiscord
  • Request Network Docs
  • Request Network API
    • Create and Pay Requests
    • Crosschain Payments
    • EasyInvoice: API Demo App
    • API Portal: Manage API Keys and Webhooks
      • Manage API Keys and Webhooks programmatically
    • Full API Reference
  • General
    • Lifecycle of a Request
    • Request Scan
    • Supported Chains
      • Smart Contract Addresses
    • Request Network Token List
  • Advanced
    • Request Network SDK
      • Get Started
        • Quickstart - Browser
        • Quickstart - Node.js
        • Installation
        • SDK Injector
        • Request Node Gateways
      • SDK Demo Apps
        • Request Invoicing
          • Pay from Safe Multisig
        • Request Checkout
        • Components
          • Create Invoice Form
          • Invoice Dashboard
          • Payment Widget
          • Add Stakeholder
      • SDK Guides
        • Request Client
          • Configure the Request Client
          • Updating a Request
          • Payment Reference
          • Compute a Request ID without creating the request
          • Use your own signature mechanism
          • Support a new currency
          • In-Memory Requests
        • Encryption and Decryption
          • Encrypt with a wallet signature using Lit Protocol
          • Encrypt with an Ethereum private key
          • Share an encrypted request
        • Payment
          • Detect a payment
          • Native Payment
          • Conversion Payment
          • Declarative Payment
          • Configuring Payment Fees
          • Single Request Forwarder
          • Batch Payment
          • Swap-to-Pay Payment
          • Swap-to-Conversion Payment
          • Transferable Receivable Payment
          • Meta Payments
          • Escrow Payment
          • Streaming Payment
          • Pay through a proxy-contract with a multisig
          • Hinkal Private Payments
        • Mobile using Expo
      • SDK Reference
        • request-client.js
          • RequestNetwork
            • createRequest()
            • computeRequestId()
            • fromRequestId()
            • fromIdentity()
            • fromTopic()
          • Request
            • waitForConfirmation()
            • getData()
            • refresh()
            • cancel()
            • accept()
            • increaseExpectedAmountRequest()
            • reduceExpectedAmountRequest()
          • IIdentity
          • IRequestDataWithEvents
          • PaymentReferenceCalculator
        • payment-processor
          • payRequest()
        • web3-signature
          • Web3SignatureProvider
        • epk-signature
          • EthereumPrivateKeySignatureProvider
        • epk-decryption
          • EthereumPrivateKeyDecryptionProvider
    • Protocol Overview
      • SDK and Request Node Overview
      • Payment Networks
      • Private Requests using Encryption
      • Smart Contracts Overview
    • Internal SDK Architecture
      • Request Logic
      • Advanced Logic
      • Transaction
      • Data-access
      • Storage
      • Data flow
      • Request IPFS network
  • FAQ
  • Glossary
  • Contributing
Powered by GitBook
On this page

Was this helpful?

Edit on GitHub
Export as PDF
  1. Advanced
  2. Request Network SDK
  3. SDK Guides
  4. Payment

Meta Payments

PreviousTransferable Receivable PaymentNextEscrow Payment

Last updated 8 months ago

Was this helpful?

Tests:

https://github.com/RequestNetwork/requestNetwork/blob/master/packages/advanced-logic/test/extensions/payment-network/meta.test.ts
import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types';
import { deepCopy } from '@requestnetwork/utils';
import { CurrencyManager, UnsupportedCurrencyError } from '@requestnetwork/currency';

import * as DataConversionERC20FeeAddData from '../../utils/payment-network/erc20/any-to-erc20-proxy-add-data-generator';
import * as MetaCreate from '../../utils/payment-network/meta-pn-data-generator';
import * as TestData from '../../utils/test-data-generator';
import MetaPaymentNetwork from '../../../src/extensions/payment-network/meta';

const metaPn = new MetaPaymentNetwork(CurrencyManager.getDefault());
const baseParams = {
  feeAddress: '0x0000000000000000000000000000000000000001',
  feeAmount: '0',
  paymentAddress: '0x0000000000000000000000000000000000000002',
  refundAddress: '0x0000000000000000000000000000000000000003',
  salt: 'ea3bc7caf64110ca',
  network: 'rinkeby',
  acceptedTokens: ['0xFab46E002BbF0b4509813474841E0716E6730136'],
  maxRateTimespan: 1000000,
} as ExtensionTypes.PnAnyToErc20.ICreationParameters;
const otherBaseParams = {
  ...baseParams,
  salt: 'ea3bc7caf64110cb',
} as ExtensionTypes.PnAnyToErc20.ICreationParameters;

/* eslint-disable @typescript-eslint/no-unused-expressions */
describe('extensions/payment-network/meta', () => {
  describe('createCreationAction', () => {
    it('can create a create action with all parameters', () => {
      expect(
        metaPn.createCreationAction({
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [baseParams, otherBaseParams],
        }),
      ).toEqual({
        action: 'create',
        id: ExtensionTypes.PAYMENT_NETWORK_ID.META,
        parameters: {
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [baseParams, otherBaseParams],
        },
        version: '0.1.0',
      });
    });

    it('can create a create action without fee parameters', () => {
      expect(
        metaPn.createCreationAction({
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
            { ...baseParams, feeAddress: undefined, feeAmount: undefined },
            otherBaseParams,
          ],
        }),
      ).toEqual({
        action: 'create',
        id: ExtensionTypes.PAYMENT_NETWORK_ID.META,
        parameters: {
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
            { ...baseParams, feeAddress: undefined, feeAmount: undefined },
            otherBaseParams,
          ],
        },
        version: '0.1.0',
      });
    });

    it('cannot createCreationAction with duplicated salt', () => {
      expect(() => {
        metaPn.createCreationAction({
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [baseParams, baseParams],
        });
      }).toThrowError('Duplicate payment network identifier (salt)');
    });

    it('cannot createCreationAction with payment address not an ethereum address', () => {
      expect(() => {
        metaPn.createCreationAction({
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
            { ...baseParams, paymentAddress: 'not an ethereum address' },
            otherBaseParams,
          ],
        });
      }).toThrowError("paymentAddress 'not an ethereum address' is not a valid address");
    });

    it('cannot createCreationAction with refund address not an ethereum address', () => {
      expect(() => {
        metaPn.createCreationAction({
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
            { ...baseParams, refundAddress: 'not an ethereum address' },
            otherBaseParams,
          ],
        });
      }).toThrowError("refundAddress 'not an ethereum address' is not a valid address");
    });

    it('cannot createCreationAction with fee address not an ethereum address', () => {
      expect(() => {
        metaPn.createCreationAction({
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
            { ...baseParams, feeAddress: 'not an ethereum address' },
            otherBaseParams,
          ],
        });
      }).toThrowError('feeAddress is not a valid address');
    });

    it('cannot createCreationAction with invalid fee amount', () => {
      expect(() => {
        metaPn.createCreationAction({
          [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY]: [
            { ...baseParams, feeAmount: '-2000' },
            otherBaseParams,
          ],
        });
      }).toThrowError('feeAmount is not a valid amount');
    });
  });

  describe('applyActionToExtension', () => {
    describe('applyActionToExtension/create', () => {
      it('can applyActionToExtensions of creation', () => {
        // 'new extension state wrong'
        expect(
          metaPn.applyActionToExtension(
            MetaCreate.requestStateNoExtensions.extensions,
            MetaCreate.actionCreationMultipleAnyToErc20,
            MetaCreate.requestStateNoExtensions,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          ),
        ).toEqual(MetaCreate.extensionFullStateMultipleAnyToErc20);
      });

      it('cannot applyActionToExtensions of creation with a previous state', () => {
        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestFullStateCreated.extensions,
            MetaCreate.actionCreationMultipleAnyToErc20,
            MetaCreate.requestFullStateCreated,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError('This extension has already been created');
      });

      it('cannot applyActionToExtensions of creation on a non supported currency', () => {
        const requestCreatedNoExtension: RequestLogicTypes.IRequest = deepCopy(
          TestData.requestCreatedNoExtension,
        );
        requestCreatedNoExtension.currency = {
          type: RequestLogicTypes.CURRENCY.ERC20,
          value: '0x967da4048cD07aB37855c090aAF366e4ce1b9F48', // OCEAN token address
          network: 'mainnet',
        };

        expect(() => {
          metaPn.applyActionToExtension(
            TestData.requestCreatedNoExtension.extensions,
            MetaCreate.actionCreationMultipleAnyToErc20,
            requestCreatedNoExtension,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(
          'The currency (OCEAN-mainnet, 0x967da4048cD07aB37855c090aAF366e4ce1b9F48) of the request is not supported for this payment network.',
        );
      });

      it('cannot applyActionToExtensions of creation with payment address not valid', () => {
        const actionWithInvalidAddress = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
        actionWithInvalidAddress.parameters[
          ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
        ][0].paymentAddress = DataConversionERC20FeeAddData.invalidAddress;

        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateNoExtensions.extensions,
            actionWithInvalidAddress,
            MetaCreate.requestStateNoExtensions,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(
          `paymentAddress '${DataConversionERC20FeeAddData.invalidAddress}' is not a valid address`,
        );
      });

      it('cannot applyActionToExtensions of creation with no tokens accepted', () => {
        const actionWithInvalidToken = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
        actionWithInvalidToken.parameters[
          ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
        ][0].acceptedTokens = [];

        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateNoExtensions.extensions,
            actionWithInvalidToken,
            MetaCreate.requestStateNoExtensions,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError('acceptedTokens is required');
      });

      it('cannot applyActionToExtensions of creation with token address not valid', () => {
        const actionWithInvalidToken = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
        actionWithInvalidToken.parameters[
          ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
        ][0].acceptedTokens = ['invalid address'];

        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateNoExtensions.extensions,
            actionWithInvalidToken,
            MetaCreate.requestStateNoExtensions,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError('acceptedTokens must contains only valid ethereum addresses');
      });

      it('cannot applyActionToExtensions of creation with refund address not valid', () => {
        const testnetRefundAddress = deepCopy(MetaCreate.actionCreationMultipleAnyToErc20);
        testnetRefundAddress.parameters[
          ExtensionTypes.PAYMENT_NETWORK_ID.ANY_TO_ERC20_PROXY
        ][0].refundAddress = DataConversionERC20FeeAddData.invalidAddress;

        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateNoExtensions.extensions,
            testnetRefundAddress,
            MetaCreate.requestStateNoExtensions,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(
          `refundAddress '${DataConversionERC20FeeAddData.invalidAddress}' is not a valid address`,
        );
      });
      it('keeps the version used at creation', () => {
        const newState = metaPn.applyActionToExtension(
          {},
          { ...MetaCreate.actionCreationMultipleAnyToErc20, version: 'ABCD' },
          MetaCreate.requestStateNoExtensions,
          TestData.otherIdRaw.identity,
          TestData.arbitraryTimestamp,
        );
        expect(newState[metaPn.extensionId].version).toBe('ABCD');
      });

      it('requires a version at creation', () => {
        expect(() => {
          metaPn.applyActionToExtension(
            {},
            { ...MetaCreate.actionCreationMultipleAnyToErc20, version: '' },
            MetaCreate.requestStateNoExtensions,
            TestData.otherIdRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError('version is required at creation');
      });
    });

    describe('applyActionToExtension/applyApplyActionToExtension', () => {
      it('can applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress', () => {
        expect(
          metaPn.applyActionToExtension(
            MetaCreate.requestStateCreatedMissingAddress.extensions,
            MetaCreate.actionApplyActionToPn,
            MetaCreate.requestStateCreatedMissingAddress,
            TestData.payeeRaw.identity,
            TestData.arbitraryTimestamp,
          ),
        ).toEqual(MetaCreate.extensionStateWithApplyAddPaymentAddressAfterCreation);
      });

      it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress without a previous state', () => {
        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateNoExtensions.extensions,
            MetaCreate.actionApplyActionToPn,
            MetaCreate.requestStateNoExtensions,
            TestData.payeeRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(`No payment network with identifier ${MetaCreate.salt2}`);
      });

      it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress without a payee', () => {
        const previousState = deepCopy(MetaCreate.requestStateCreatedMissingAddress);
        previousState.payee = undefined;

        expect(() => {
          metaPn.applyActionToExtension(
            previousState.extensions,
            MetaCreate.actionApplyActionToPn,
            previousState,
            TestData.payeeRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(`The request must have a payee`);
      });

      it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress signed by someone else than the payee', () => {
        const previousState = deepCopy(MetaCreate.requestStateCreatedMissingAddress);

        expect(() => {
          metaPn.applyActionToExtension(
            previousState.extensions,
            MetaCreate.actionApplyActionToPn,
            previousState,
            TestData.payerRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(`The signer must be the payee`);
      });

      it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress with payment address already given', () => {
        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestFullStateCreated.extensions,
            MetaCreate.actionApplyActionToPn,
            MetaCreate.requestFullStateCreated,
            TestData.payeeRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(`Payment address already given`);
      });

      it('cannot applyActionToExtensions of applyApplyActionToExtension for addPaymentAddress with payment address not valid', () => {
        const actionWithInvalidAddress = deepCopy(MetaCreate.actionApplyActionToPn);
        actionWithInvalidAddress.parameters.parameters.paymentAddress =
          DataConversionERC20FeeAddData.invalidAddress;

        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateCreatedMissingAddress.extensions,
            actionWithInvalidAddress,
            MetaCreate.requestStateCreatedMissingAddress,
            TestData.payeeRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(
          `paymentAddress '${DataConversionERC20FeeAddData.invalidAddress}' is not a valid address`,
        );
      });

      it('cannot applyActionToExtensions applyApplyActionToExtension when the pn identifier is wrong', () => {
        const actionWithInvalidPnIdentifier = deepCopy(MetaCreate.actionApplyActionToPn);
        actionWithInvalidPnIdentifier.parameters.pnIdentifier = 'wrongId';

        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateCreatedMissingAddress.extensions,
            actionWithInvalidPnIdentifier,
            MetaCreate.requestStateCreatedMissingAddress,
            TestData.payeeRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(`No payment network with identifier wrongId`);
      });

      it('cannot applyActionToExtensions applyApplyActionToExtension when the action does not exists on the sub pn', () => {
        const actionWithInvalidPnAction = deepCopy(MetaCreate.actionApplyActionToPn);
        actionWithInvalidPnAction.parameters.action = 'wrongAction' as ExtensionTypes.ACTION;

        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateCreatedMissingAddress.extensions,
            actionWithInvalidPnAction,
            MetaCreate.requestStateCreatedMissingAddress,
            TestData.payeeRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(`Unknown action: wrongAction`);
      });
    });
  });

  describe('declarative tests', () => {
    describe('applyActionToExtension/declareSentPayment', () => {
      it('can applyActionToExtensions of declareSentPayment', () => {
        expect(
          metaPn.applyActionToExtension(
            MetaCreate.requestFullStateCreated.extensions,
            MetaCreate.actionDeclareSentPayment,
            MetaCreate.requestFullStateCreated,
            TestData.payerRaw.identity,
            TestData.arbitraryTimestamp,
          ),
        ).toEqual(MetaCreate.extensionStateWithDeclaredSent);
      });

      it('cannot applyActionToExtensions of declareSentPayment without a previous state', () => {
        expect(() => {
          metaPn.applyActionToExtension(
            MetaCreate.requestStateNoExtensions.extensions,
            MetaCreate.actionDeclareSentPayment,
            MetaCreate.requestStateNoExtensions,
            TestData.payerRaw.identity,
            TestData.arbitraryTimestamp,
          );
        }).toThrowError(`The extension should be created before receiving any other action`);
      });
    });
  });
});