Skip to content

Commit

Permalink
feat: add cancel stream function (#884)
Browse files Browse the repository at this point in the history
  • Loading branch information
bertux authored and rom1trt committed Jul 28, 2022
1 parent 76ea2e6 commit 56afdcf
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 31 deletions.
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 signer. Defaults to window.ethereum.
* @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:
// - 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 signer. Defaults to window.ethereum.
* @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));

// Stopping fDAIX stream request
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');
});
});
});

0 comments on commit 56afdcf

Please sign in to comment.