Skip to content

Commit

Permalink
feat: add prepare and encode functions for SuperFluid (#896)
Browse files Browse the repository at this point in the history
  • Loading branch information
bertux committed Aug 11, 2022
1 parent 6b54411 commit 895c498
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 51 deletions.
43 changes: 43 additions & 0 deletions packages/payment-processor/src/payment/encoder-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { prepareEthFeeProxyPaymentTransaction } from './eth-fee-proxy';
import { prepareAnyToEthProxyPaymentTransaction } from './any-to-eth-proxy';
import { IConversionPaymentSettings } from '.';
import { getPaymentNetworkExtension } from './utils';
import { prepareErc777StreamPaymentTransaction } from './erc777-stream';

/**
* Encodes a transaction to pay a Request in generic way. ERC777 stream excepted.
* @param request the request data to pay
* @param provider the Web3 provider. Defaults to window.ethereum.
* @param options optionally, the request payment options.
*/
export function encodeRequestPayment(
request: ClientTypes.IRequestData,
provider: providers.Provider,
Expand All @@ -25,6 +32,11 @@ export function encodeRequestPayment(
}
}

/**
* Encodes a transaction to pay a Request in generic way without swap.
* @param request the request data to pay
* @param options optionally, the request payment options.
*/
export function encodeRequestPaymentWithoutSwap(
request: ClientTypes.IRequestData,
options?: IRequestPaymentOptions,
Expand Down Expand Up @@ -98,6 +110,37 @@ export function encodeRequestPaymentWithoutSwap(
}
}

/**
* Encodes a transaction to pay a Request with ERC777 stream.
* @param request the request data to pay
* @param provider the Web3 provider. Defaults to window.ethereum.
* @param options optionally, the request payment options.
*/
export async function encodeRequestPaymentWithStream(
request: ClientTypes.IRequestData,
provider: providers.Provider,
options?: IRequestPaymentOptions,
): Promise<IPreparedTransaction> {
const paymentNetwork = getPaymentNetworkExtension(request)?.id;
const overrides = options?.overrides ? options.overrides : {};

switch (paymentNetwork) {
case ExtensionTypes.ID.PAYMENT_NETWORK_ERC777_STREAM:
return {
...(await prepareErc777StreamPaymentTransaction(request, provider)),
...overrides,
};
default:
throw new Error(`Payment network {paymentNetwork} does not support stream`);
}
}

/**
* Encodes a transaction to pay a Request in generic way with swap.
* @param request the request data to pay
* @param provider the Web3 provider. Defaults to window.ethereum.
* @param options optionally, the request payment options.
*/
export function encodeRequestPaymentWithSwap(
request: ClientTypes.IRequestData,
provider: providers.Provider,
Expand Down
103 changes: 77 additions & 26 deletions packages/payment-processor/src/payment/erc777-stream.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContractTransaction, Signer, Overrides } from 'ethers';
import { ContractTransaction, Signer, Overrides, providers } from 'ethers';

import { ClientTypes, ExtensionTypes, PaymentTypes } from '@requestnetwork/types';

Expand All @@ -9,6 +9,7 @@ import {
validateRequest,
} from './utils';
import { Framework } from '@superfluid-finance/sdk-core';
import { IPreparedTransaction } from './prepared-transaction';

export const RESOLVER_ADDRESS = '0x913bbCFea2f347a24cfCA441d483E7CBAc8De3Db';
// Superfluid payments of requests use the generic field `userData` to index payments.
Expand All @@ -18,7 +19,7 @@ export const USERDATA_PREFIX = '0xbeefac';

/**
* Processes a transaction to pay an ERC777 stream Request.
* @param request
* @param request the request to pay
* @param signer the Web3 signer. Defaults to window.ethereum.
* @param overrides optionally, override default transaction values, like gas.
*/
Expand All @@ -27,25 +28,16 @@ export async function payErc777StreamRequest(
signer: Signer,
overrides?: Overrides,
): Promise<ContractTransaction> {
const id = getPaymentNetworkExtension(request)?.id;
if (id !== ExtensionTypes.ID.PAYMENT_NETWORK_ERC777_STREAM) {
throw new Error('Not a supported ERC777 payment network request');
}
validateRequest(request, PaymentTypes.PAYMENT_NETWORK_ID.ERC777_STREAM);
const sf = await getSuperFluidFramework(request, signer);
// FIXME: according to specs PR https://github.com/RequestNetwork/requestNetwork/pull/688
// in file packages/advanced-logic/specs/payment-network-erc777-stream-0.1.0.md
// Below are the SF actions to add in the BatchCall:
// - use expectedStartDate to compute offset between start of invoicing and start of streaming
// - start fee streaming
const streamPayOp = await getStartStreamOp(sf, request, overrides);
const batchCall = sf.batchCall([streamPayOp]);
return batchCall.exec(signer);
const { data, to, value } = await prepareErc777StreamPaymentTransaction(
request,
signer.provider ?? getProvider(),
);
return signer.sendTransaction({ data, to, value, ...overrides });
}

/**
* Processes a transaction to complete an ERC777 stream paying a Request.
* @param request
* @param request the request to pay
* @param signer the Web3 signer. Defaults to window.ethereum.
* @param overrides optionally, override default transaction values, like gas.
*/
Expand All @@ -59,7 +51,7 @@ export async function completeErc777StreamRequest(
throw new Error('Not a supported ERC777 payment network request');
}
validateRequest(request, PaymentTypes.PAYMENT_NETWORK_ID.ERC777_STREAM);
const sf = await getSuperFluidFramework(request, signer);
const sf = await getSuperFluidFramework(request, signer.provider ?? getProvider());
// FIXME: according to specs PR https://github.com/RequestNetwork/requestNetwork/pull/688
// in file packages/advanced-logic/specs/payment-network-erc777-stream-0.1.0.md
// Below are the SF actions to add in the BatchCall :
Expand All @@ -70,33 +62,49 @@ export async function completeErc777StreamRequest(
return batchCall.exec(signer);
}

async function getSuperFluidFramework(request: ClientTypes.IRequestData, signer: Signer) {
/**
* Encodes the call to pay a request through the ERC20 fee proxy contract, can be used with a Multisig contract.
* @param request the request to pay
* @param provider the Web3 provider. Defaults to window.ethereum.
*/
async function getSuperFluidFramework(
request: ClientTypes.IRequestData,
provider: providers.Provider,
) {
const isNetworkPrivate = request.currencyInfo.network === 'private';
const networkName = isNetworkPrivate ? 'custom' : request.currencyInfo.network;
return await Framework.create({
networkName,
provider: signer.provider ?? getProvider(),
provider: provider,
dataMode: isNetworkPrivate ? 'WEB3_ONLY' : undefined,
resolverAddress: isNetworkPrivate ? RESOLVER_ADDRESS : undefined,
protocolReleaseVersion: isNetworkPrivate ? 'test' : undefined,
});
}
async function getStartStreamOp(
sf: Framework,
request: ClientTypes.IRequestData,
overrides?: Overrides,
) {

/**
* Get from SuperFluid framework the operation to start paying a request.
* @param sf the SuperFluid framework to use
* @param request the request to pay
*/
async function getStartStreamOp(sf: Framework, request: ClientTypes.IRequestData) {
const superToken = await sf.loadSuperToken(request.currencyInfo.value);
const { paymentReference, paymentAddress, expectedFlowRate } = getRequestPaymentValues(request);
return sf.cfaV1.createFlow({
flowRate: expectedFlowRate ?? '0',
receiver: paymentAddress,
superToken: superToken.address,
userData: `${USERDATA_PREFIX}${paymentReference}`,
overrides: overrides,
});
}

/**
* Get from SuperFluid framework the operation to stop paying a request.
* @param sf the SuperFluid framework to use
* @param signer the Web3 signer. Defaults to window.ethereum.
* @param request the request to pay
* @param overrides optionally, override default transaction values, like gas.
*/
async function getStopStreamOp(
sf: Framework,
signer: Signer,
Expand All @@ -113,3 +121,46 @@ async function getStopStreamOp(
overrides: overrides,
});
}

/**
* Encodes the call to pay a request through the ERC777 SuperFluid stream contract.
* @param request the request to pay
* @param sf the SuperFluid framework to use
*/
export async function encodePayErc777StreamRequest(
request: ClientTypes.IRequestData,
sf: Framework,
): Promise<string> {
// FIXME: according to specs PR https://github.com/RequestNetwork/requestNetwork/pull/688
// in file packages/advanced-logic/specs/payment-network-erc777-stream-0.1.0.md
// Below are the SF actions to add in the BatchCall:
// - use expectedStartDate to compute offset between start of invoicing and start of streaming
// - start fee streaming
const streamPayOp = await getStartStreamOp(sf, request);
const batchCall = sf.batchCall([streamPayOp]);

const operationStructArray = await Promise.all(batchCall.getOperationStructArrayPromises);
return batchCall.host.hostContract.interface.encodeFunctionData('batchCall', [
operationStructArray,
]);
}
/**
* Prepare the transaction to pay a request through the ERC777 SuperFluid stream contract.
* @param request the request to pay
* @param provider the Web3 provider. Defaults to window.ethereum.
*/
export async function prepareErc777StreamPaymentTransaction(
request: ClientTypes.IRequestData,
provider: providers.Provider,
): Promise<IPreparedTransaction> {
validateRequest(request, PaymentTypes.PAYMENT_NETWORK_ID.ERC777_STREAM);
const sf = await getSuperFluidFramework(request, provider);

const encodedTx = await encodePayErc777StreamRequest(request, sf);

return {
data: encodedTx,
to: sf.host.hostContract.address,
value: 0,
};
}
78 changes: 56 additions & 22 deletions packages/payment-processor/test/payment/encoder-payment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
PaymentTypes,
RequestLogicTypes,
} from '@requestnetwork/types';
import { encodeRequestPayment } from '../../src';
import { encodeRequestPayment, encodeRequestPaymentWithStream } from '../../src';
import { getProxyAddress } from '../../src/payment/utils';
import {
AnyToERC20PaymentDetector,
Expand All @@ -20,6 +20,7 @@ import {
erc20SwapToPayArtifact,
erc20SwapConversionArtifact,
} from '@requestnetwork/smart-contracts';
import { DAIX_ADDRESS } from './erc777-stream.test';

/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable @typescript-eslint/await-thenable */
Expand Down Expand Up @@ -59,6 +60,8 @@ const alphaSwapConversionSettings = {

const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat';
const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732';
const expectedFlowRate = '100000';
const expectedStartDate = '1643041225';
const provider = new providers.JsonRpcProvider('http://localhost:8545');
const wallet = Wallet.fromMnemonic(mnemonic).connect(provider);

Expand Down Expand Up @@ -122,6 +125,30 @@ const validRequestERC20FeeProxy: ClientTypes.IRequestData = {
},
};

const validRequestERC777Stream: ClientTypes.IRequestData = {
...baseValidRequest,
currency: 'DAIx',
currencyInfo: {
network: 'private',
type: RequestLogicTypes.CURRENCY.ERC777,
value: DAIX_ADDRESS,
},
extensions: {
[PaymentTypes.PAYMENT_NETWORK_ID.ERC777_STREAM]: {
events: [],
id: ExtensionTypes.ID.PAYMENT_NETWORK_ERC777_STREAM,
type: ExtensionTypes.TYPE.PAYMENT_NETWORK,
values: {
expectedStartDate,
expectedFlowRate,
paymentAddress,
salt: 'salt',
},
version: '0.1.0',
},
},
};

const validRequestERC20ConversionProxy: ClientTypes.IRequestData = {
...baseValidRequest,
currency: 'EUR',
Expand Down Expand Up @@ -221,7 +248,7 @@ const validRequestEthConversionProxy: ClientTypes.IRequestData = {

describe('Payment encoder handles ERC20 Proxy', () => {
it('Should return a valid transaction', async () => {
const paymentTransaction = await encodeRequestPayment(baseValidRequest, provider);
const paymentTransaction = encodeRequestPayment(baseValidRequest, provider);

const proxyAddress = getProxyAddress(
baseValidRequest,
Expand All @@ -238,7 +265,7 @@ describe('Payment encoder handles ERC20 Proxy', () => {

describe('Payment encoder handles ERC20 Fee Proxy', () => {
it('Should return a valid transaction', async () => {
const paymentTransaction = await encodeRequestPayment(validRequestERC20FeeProxy, provider);
const paymentTransaction = encodeRequestPayment(validRequestERC20FeeProxy, provider);

const proxyAddress = getProxyAddress(
validRequestERC20FeeProxy,
Expand All @@ -255,13 +282,9 @@ describe('Payment encoder handles ERC20 Fee Proxy', () => {

describe('Payment encoder handles ERC20 Conversion Proxy', () => {
it('Should return a valid transaction', async () => {
let paymentTransaction = await encodeRequestPayment(
validRequestERC20ConversionProxy,
provider,
{
conversion: alphaConversionSettings,
},
);
let paymentTransaction = encodeRequestPayment(validRequestERC20ConversionProxy, provider, {
conversion: alphaConversionSettings,
});

const proxyAddress = getProxyAddress(
validRequestERC20ConversionProxy,
Expand All @@ -283,7 +306,7 @@ describe('Payment encoder handles ERC20 Conversion Proxy', () => {

describe('Payment encoder handles ERC20 Swap Proxy', () => {
it('Should return a valid transaction', async () => {
let paymentTransaction = await encodeRequestPayment(validRequestERC20FeeProxy, provider, {
let paymentTransaction = encodeRequestPayment(validRequestERC20FeeProxy, provider, {
swap: alphaSwapSettings,
});

Expand All @@ -301,14 +324,10 @@ describe('Payment encoder handles ERC20 Swap Proxy', () => {

describe('Payment encoder handles ERC20 Swap & Conversion Proxy', () => {
it('Should return a valid transaction', async () => {
let paymentTransaction = await encodeRequestPayment(
validRequestERC20ConversionProxy,
provider,
{
swap: alphaSwapConversionSettings,
conversion: alphaConversionSettings,
},
);
let paymentTransaction = encodeRequestPayment(validRequestERC20ConversionProxy, provider, {
swap: alphaSwapConversionSettings,
conversion: alphaConversionSettings,
});

const proxyAddress = erc20SwapConversionArtifact.getAddress(
alphaConversionSettings.currency.network,
Expand Down Expand Up @@ -338,7 +357,7 @@ describe('Payment encoder handles ERC20 Swap & Conversion Proxy', () => {

describe('Payment encoder handles Eth Proxy', () => {
it('Should return a valid transaction', async () => {
let paymentTransaction = await encodeRequestPayment(validRequestEthProxy, provider);
let paymentTransaction = encodeRequestPayment(validRequestEthProxy, provider);

const proxyAddress = getProxyAddress(
validRequestEthProxy,
Expand All @@ -355,7 +374,7 @@ describe('Payment encoder handles Eth Proxy', () => {

describe('Payment encoder handles Eth Fee Proxy', () => {
it('Should return a valid transaction', async () => {
let paymentTransaction = await encodeRequestPayment(validRequestEthFeeProxy, provider);
let paymentTransaction = encodeRequestPayment(validRequestEthFeeProxy, provider);

const proxyAddress = getProxyAddress(
validRequestEthFeeProxy,
Expand All @@ -372,7 +391,7 @@ describe('Payment encoder handles Eth Fee Proxy', () => {

describe('Payment encoder handles Eth Conversion Proxy', () => {
it('Should return a valid transaction', async () => {
let paymentTransaction = await encodeRequestPayment(validRequestEthConversionProxy, provider, {
let paymentTransaction = encodeRequestPayment(validRequestEthConversionProxy, provider, {
conversion: ethConversionSettings,
});

Expand All @@ -393,3 +412,18 @@ describe('Payment encoder handles Eth Conversion Proxy', () => {
);
});
});

describe('Payment encoder handles ERC777 Stream', () => {
it('Should return a valid transaction', async () => {
const paymentTransaction = await encodeRequestPaymentWithStream(
validRequestERC777Stream,
provider,
);

expect(paymentTransaction).toEqual({
data: '0x6ad3ca7d00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c90000000000000000000000000dd64f3458f32a277ccd94bfbdb31952b84d99ee000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a462fc305e0000000000000000000000007d782d2cc2755ca324de57d42e28cc63278dfe12000000000000000000000000f17f52151ebef6c7334fad080c5704d77216b73200000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bbeefac86dfbccad783599a000000000000000000000000000000000000000000',
to: '0x75076e4fbba61f65efB41D64e45cFF340b1e518A',
value: 0,
});
});
});
Loading

0 comments on commit 895c498

Please sign in to comment.