Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cancel stream function #884

Merged
merged 9 commits into from
Jul 12, 2022
99 changes: 76 additions & 23 deletions packages/payment-processor/src/payment/erc777-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import {
} from './utils';
import { Framework } from '@superfluid-finance/sdk-core';

export const resolverAddress = '0x913bbCFea2f347a24cfCA441d483E7CBAc8De3Db';
export const RESOLVER_ADDRESS = '0x913bbCFea2f347a24cfCA441d483E7CBAc8De3Db';
// Superfluid payments of requests use the generic field `userData` to index payments.
// Since it's a multi-purpose field, payments will use a fix-prefix heading the payment reference,
// in order to speed up the indexing and payment detection.
export const USERDATA_PREFIX = '0xbeefac';

/**
* Processes a transaction to pay an ERC777 stream Request.
* @param request
* @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum.
* @param signer the Web3 provider, or signer. Defaults to window.ethereum.
bertux marked this conversation as resolved.
Show resolved Hide resolved
* @param overrides optionally, override default transaction values, like gas.
*/
export async function payErc777StreamRequest(
Expand All @@ -28,35 +32,84 @@ export async function payErc777StreamRequest(
throw new Error('Not a supported ERC777 payment network request');
}
validateRequest(request, PaymentTypes.PAYMENT_NETWORK_ID.ERC777_STREAM);
const networkName =
request.currencyInfo.network === 'private' ? 'custom' : request.currencyInfo.network;
const sf = await Framework.create({
networkName,
provider: signer.provider ?? getProvider(),
dataMode: request.currencyInfo.network === 'private' ? 'WEB3_ONLY' : undefined,
resolverAddress: request.currencyInfo.network === 'private' ? resolverAddress : undefined,
protocolReleaseVersion: request.currencyInfo.network === 'private' ? 'test' : undefined,
});
const superSigner = sf.createSigner({
signer: signer,
provider: signer.provider,
});
const superToken = await sf.loadSuperToken(request.currencyInfo.value);
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 :
bertux marked this conversation as resolved.
Show resolved Hide resolved
// - 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);
}

/**
* Processes a transaction to complete an ERC777 stream paying a Request.
* @param request
* @param signer the Web3 provider, or signer. Defaults to window.ethereum.
bertux marked this conversation as resolved.
Show resolved Hide resolved
* @param overrides optionally, override default transaction values, like gas.
*/
export async function completeErc777StreamRequest(
request: ClientTypes.IRequestData,
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 expectedEndDate to compute offset between stop of invoicing and stop of streaming
// - stop fee streaming
const streamPayOp = await getStopStreamOp(sf, signer, request, overrides);
const batchCall = sf.batchCall([streamPayOp]);
return batchCall.exec(signer);
}

async function getSuperFluidFramework(request: ClientTypes.IRequestData, signer: Signer) {
const isNetworkPrivate = request.currencyInfo.network === 'private';
const networkName = isNetworkPrivate ? 'custom' : request.currencyInfo.network;
return await Framework.create({
networkName,
provider: signer.provider ?? getProvider(),
dataMode: isNetworkPrivate ? 'WEB3_ONLY' : undefined,
resolverAddress: isNetworkPrivate ? RESOLVER_ADDRESS : undefined,
protocolReleaseVersion: isNetworkPrivate ? 'test' : undefined,
});
}
async function getStartStreamOp(
sf: Framework,
request: ClientTypes.IRequestData,
overrides?: Overrides,
) {
const superToken = await sf.loadSuperToken(request.currencyInfo.value);
const { paymentReference, paymentAddress, expectedFlowRate } = getRequestPaymentValues(request);
// Superfluid payments of requests use the generic field `userData` to index payments.
// Since it's a multi-purpose field, payments will use a fix-prefix heading the payment reference,
// in order to speed up the indexing and payment detection.
const streamPayOp = sf.cfaV1.createFlow({
return sf.cfaV1.createFlow({
flowRate: expectedFlowRate ?? '0',
receiver: paymentAddress,
superToken: superToken.address,
userData: `0xbeefac${paymentReference}`,
userData: `${USERDATA_PREFIX}${paymentReference}`,
overrides: overrides,
});
}

async function getStopStreamOp(
sf: Framework,
signer: Signer,
request: ClientTypes.IRequestData,
overrides?: Overrides,
) {
const superToken = await sf.loadSuperToken(request.currencyInfo.value);
const { paymentReference, paymentAddress } = getRequestPaymentValues(request);
return sf.cfaV1.deleteFlow({
superToken: superToken.address,
sender: await signer.getAddress(),
receiver: paymentAddress,
userData: `${USERDATA_PREFIX}${paymentReference}`,
overrides: overrides,
});
const batchCall = sf.batchCall([streamPayOp]);
return batchCall.exec(superSigner);
}
58 changes: 50 additions & 8 deletions packages/payment-processor/test/payment/erc777-stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
} from '@requestnetwork/types';
import Utils from '@requestnetwork/utils';

import { payErc777StreamRequest, resolverAddress } from '../../src/payment/erc777-stream';
import {
completeErc777StreamRequest,
payErc777StreamRequest,
RESOLVER_ADDRESS,
} from '../../src/payment/erc777-stream';
import { getRequestPaymentValues } from '../../src/payment/utils';
const daiABI = require('../abis/fDAIABI');

Expand Down Expand Up @@ -120,16 +124,16 @@ describe('erc777-stream', () => {
});
});

describe('payErc777StreamRequest', () => {
it('should pay an ERC777 request with fees', async () => {
describe('Streams management', () => {
it('payErc777StreamRequest should pay an ERC777 request', async () => {
let tx;
let confirmedTx;
// initialize the superfluid framework...put custom and web3 only bc we are using ganache locally
const sf = await Framework.create({
networkName: 'custom',
provider,
dataMode: 'WEB3_ONLY',
resolverAddress: resolverAddress,
resolverAddress: RESOLVER_ADDRESS,
protocolReleaseVersion: 'test',
});

Expand Down Expand Up @@ -183,18 +187,56 @@ describe('erc777-stream', () => {
expect(confirmedTx.status).toBe(1);
expect(tx.hash).not.toBeUndefined();

const wFlowRate = await sf.cfaV1.getNetFlow({
const walletFlowRate = await sf.cfaV1.getNetFlow({
superToken: daix.address,
account: wallet.address,
providerOrSigner: provider,
});
expect(walletFlowRate).toBe(`-${expectedFlowRate}`);
const paymentFlowRate = await sf.cfaV1.getNetFlow({
superToken: daix.address,
account: paymentAddress,
providerOrSigner: provider,
});
expect(paymentFlowRate).toBe(expectedFlowRate);
});

it('completeErc777StreamRequest should complete an ERC777 request', async () => {
let tx;
let confirmedTx;
// initialize the superfluid framework...put custom and web3 only bc we are using ganache locally
const sf = await Framework.create({
networkName: 'custom',
provider,
dataMode: 'WEB3_ONLY',
resolverAddress: RESOLVER_ADDRESS,
protocolReleaseVersion: 'test',
});

// use the framework to get the SuperToken
const daix = await sf.loadSuperToken('fDAIx');

// wait 2 seconds of streaming to avoid failing
await new Promise((r) => setTimeout(r, 2000));

// Paying fDAIX stream request
bertux marked this conversation as resolved.
Show resolved Hide resolved
tx = await completeErc777StreamRequest(validRequest, wallet);
confirmedTx = await tx.wait(1);
expect(confirmedTx.status).toBe(1);
expect(tx.hash).not.toBeUndefined();

const walletFlowRate = await sf.cfaV1.getNetFlow({
superToken: daix.address,
account: wallet.address,
providerOrSigner: provider,
});
expect(wFlowRate).toBe(`-${expectedFlowRate}`);
const pFlowRate = await sf.cfaV1.getNetFlow({
expect(walletFlowRate).toBe('0');
const paymentFlowRate = await sf.cfaV1.getNetFlow({
superToken: daix.address,
account: paymentAddress,
providerOrSigner: provider,
});
expect(pFlowRate).toBe(expectedFlowRate);
expect(paymentFlowRate).toBe('0');
});
});
});