Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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');
});
});
});