• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RequestNetwork / requestNetwork / 9e37c832-794f-498e-82c4-3213f87392bd

pending completion
9e37c832-794f-498e-82c4-3213f87392bd

push

circleci

GitHub
fix: existing stream (#1019)

2334 of 2946 branches covered (79.23%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

4994 of 5384 relevant lines covered (92.76%)

20.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.94
/packages/payment-processor/src/payment/erc777-utils.ts
1
import { ContractTransaction, Signer, providers, BigNumberish, BigNumber } from 'ethers';
2

3
import { ClientTypes, ExtensionTypes } from '@requestnetwork/types';
5✔
4
import { getPaymentNetworkExtension } from '@requestnetwork/payment-detection';
5✔
5

6
import {
5✔
7
  getNetworkProvider,
8
  getProvider,
9
  validateRequest,
10
  MAX_ALLOWANCE,
11
  getRequestPaymentValues,
12
} from './utils';
13
import Token from '@superfluid-finance/sdk-core/dist/module/Token';
14
import { getSuperFluidFramework } from './erc777-stream';
5✔
15
import Operation from '@superfluid-finance/sdk-core/dist/module/Operation';
16
import { checkErc20Allowance, encodeApproveAnyErc20, getAnyErc20Balance } from './erc20';
5✔
17
import { IPreparedTransaction } from './prepared-transaction';
18

19
/**
20
 * Gets the underlying token address of an ERC777 currency based request
21
 * @param request the request that contains currency information
22
 * @param provider the web3 provider. Defaults to Etherscan
23
 */
24
export async function getRequestUnderlyingToken(
5✔
25
  request: ClientTypes.IRequestData,
26
  provider: providers.Provider = getNetworkProvider(request),
27
): Promise<Token> {
28
  const id = getPaymentNetworkExtension(request)?.id;
20✔
29
  if (id !== ExtensionTypes.PAYMENT_NETWORK_ID.ERC777_STREAM) {
20✔
30
    throw new Error('Not a supported ERC777 payment network request');
×
31
  }
32
  validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC777_STREAM);
20✔
33
  const sf = await getSuperFluidFramework(request, provider);
20✔
34
  const superToken = await sf.loadSuperToken(request.currencyInfo.value);
20✔
35
  return superToken.underlyingToken;
20✔
36
}
37

38
/**
39
 * Gets the underlying token address of an ERC777 currency based request
40
 * @param request the request that contains currency information
41
 * @param provider the web3 provider. Defaults to Etherscan
42
 */
43
export async function getUnderlyingTokenBalanceOf(
5✔
44
  request: ClientTypes.IRequestData,
45
  address: string,
46
  provider: providers.Provider = getNetworkProvider(request),
47
): Promise<BigNumberish> {
48
  const underlyingToken = await getRequestUnderlyingToken(request, provider);
10✔
49
  return getAnyErc20Balance(underlyingToken.address, address, provider);
10✔
50
}
51

52
/**
53
 * Check if the user has the specified amount of underlying token
54
 * @param request the request that contains currency information
55
 * @param address token owner
56
 * @param provider the web3 provider. Defaults to Etherscan
57
 * @param amount the required amount
58
 */
59
export async function hasEnoughUnderlyingToken(
5✔
60
  request: ClientTypes.IRequestData,
61
  address: string,
62
  provider: providers.Provider = getNetworkProvider(request),
63
  amount: BigNumber,
64
): Promise<boolean> {
65
  const balance = await getUnderlyingTokenBalanceOf(request, address, provider);
4✔
66
  return amount.lte(balance);
4✔
67
}
68

69
/**
70
 * Determine whether or not the supertoken has enough allowance
71
 * @param request the request that contains currency information
72
 * @param address token owner
73
 * @param provider the web3 provider. Defaults to Etherscan
74
 * @param amount of token required
75
 */
76
export async function checkSuperTokenUnderlyingAllowance(
5✔
77
  request: ClientTypes.IRequestData,
78
  address: string,
79
  provider: providers.Provider = getNetworkProvider(request),
80
  amount: BigNumber = MAX_ALLOWANCE,
81
): Promise<boolean> {
82
  const underlyingToken = await getRequestUnderlyingToken(request, provider);
4✔
83
  return checkErc20Allowance(
4✔
84
    address,
85
    request.currencyInfo.value,
86
    provider,
87
    underlyingToken.address,
88
    amount,
89
  );
90
}
91

92
/**
93
 * Get the SF operation to approve the supertoken to spend underlying tokens
94
 * @param request the request that contains currency information
95
 * @param provider the web3 provider. Defaults to Etherscan
96
 * @param amount to allow, defalts to max allowance
97
 */
98
export async function prepareApproveUnderlyingToken(
5✔
99
  request: ClientTypes.IRequestData,
100
  provider: providers.Provider = getNetworkProvider(request),
101
  amount: BigNumber = MAX_ALLOWANCE,
102
): Promise<IPreparedTransaction> {
103
  const underlyingToken = await getRequestUnderlyingToken(request, provider);
3✔
104
  return {
3✔
105
    data: encodeApproveAnyErc20(
106
      underlyingToken.address,
107
      request.currencyInfo.value,
108
      provider,
109
      amount,
110
    ),
111
    to: underlyingToken.address,
112
    value: 0,
113
  };
114
}
115

116
/**
117
 * Get the SF operation to Wrap the underlying asset into supertoken
118
 * @param request the request that contains currency information
119
 * @param address the user address
120
 * @param provider the web3 provider
121
 * @param amount to allow, defalts to max allowance
122
 */
123
export async function getWrapUnderlyingTokenOp(
5✔
124
  request: ClientTypes.IRequestData,
125
  provider: providers.Provider = getNetworkProvider(request),
126
  amount: BigNumber,
127
): Promise<Operation> {
128
  const sf = await getSuperFluidFramework(request, provider);
1✔
129
  const superToken = await sf.loadSuperToken(request.currencyInfo.value);
1✔
130
  return superToken.upgrade({
1✔
131
    amount: amount.toString(),
132
  });
133
}
134

135
/**
136
 * Approve the supertoken to spend the speicified amount of underlying token
137
 * @param request the request that contains currency information
138
 * @param signer the web3 signer
139
 * @param amount to allow, defaults to max allowance
140
 * @returns
141
 */
142
export async function approveUnderlyingToken(
5✔
143
  request: ClientTypes.IRequestData,
144
  signer: Signer,
145
  amount: BigNumber = MAX_ALLOWANCE,
146
): Promise<ContractTransaction> {
147
  if (
3✔
148
    !(await hasEnoughUnderlyingToken(
149
      request,
150
      await signer.getAddress(),
151
      signer.provider ?? getProvider(),
152
      amount,
153
    ))
154
  ) {
155
    throw new Error('Sender does not have enough underlying token');
×
156
  }
157
  const preparedTx = await prepareApproveUnderlyingToken(
3✔
158
    request,
159
    signer.provider ?? getProvider(),
160
    amount,
161
  );
162
  return signer.sendTransaction(preparedTx);
3✔
163
}
164

165
/**
166
 * Prepare the wrap transaction of the specified amount of underlying token into supertoken
167
 * @param request the request that contains currency information
168
 * @param provider the web3 provider
169
 * @param amount to allow, defaults to max allowance
170
 * @returns
171
 */
172
export async function prepareWrapUnderlyingToken(
5✔
173
  request: ClientTypes.IRequestData,
174
  provider: providers.Provider = getNetworkProvider(request),
175
  amount: BigNumber = MAX_ALLOWANCE,
176
): Promise<IPreparedTransaction> {
177
  const wrapOp = await getWrapUnderlyingTokenOp(request, provider, amount);
1✔
178
  return (await wrapOp.populateTransactionPromise) as IPreparedTransaction;
1✔
179
}
180

181
/**
182
 * Wrap the speicified amount of underlying token into supertokens
183
 * @param request the request that contains currency information
184
 * @param signer the web3 signer
185
 * @param amount to allow, defaults to max allowance
186
 * @returns
187
 */
188
export async function wrapUnderlyingToken(
5✔
189
  request: ClientTypes.IRequestData,
190
  signer: Signer,
191
  amount: BigNumber = MAX_ALLOWANCE,
192
): Promise<ContractTransaction> {
193
  const senderAddress = await signer.getAddress();
2✔
194
  const provider = signer.provider ?? getProvider();
2✔
195
  if (!(await checkSuperTokenUnderlyingAllowance(request, senderAddress, provider, amount))) {
2✔
196
    throw new Error('Supertoken not allowed to wrap this amount of underlying');
1✔
197
  }
198
  if (!(await hasEnoughUnderlyingToken(request, senderAddress, provider, amount))) {
1✔
199
    throw new Error('Sender does not have enough underlying token');
×
200
  }
201
  const preparedTx = await prepareWrapUnderlyingToken(
1✔
202
    request,
203
    signer.provider ?? getProvider(),
204
    amount,
205
  );
206
  return signer.sendTransaction(preparedTx);
1✔
207
}
208

209
/**
210
 * Prepare the unwrapping transaction of the supertoken (ERC777) into underlying asset (ERC20)
211
 * @param request the request that contains currency information
212
 * @param provider the web3 provider
213
 * @param amount to unwrap
214
 */
215
export async function prepareUnwrapSuperToken(
5✔
216
  request: ClientTypes.IRequestData,
217
  provider: providers.Provider = getNetworkProvider(request),
218
  amount: BigNumber,
219
): Promise<IPreparedTransaction> {
220
  const sf = await getSuperFluidFramework(request, provider);
1✔
221
  const superToken = await sf.loadSuperToken(request.currencyInfo.value);
1✔
222
  const underlyingToken = await getRequestUnderlyingToken(request, provider);
1✔
223

224
  if (underlyingToken.address === superToken.address) {
1✔
225
    throw new Error('This is a native super token');
×
226
  }
227

228
  const downgradeOp = superToken.downgrade({
1✔
229
    amount: amount.toString(),
230
  });
231
  return (await downgradeOp.populateTransactionPromise) as IPreparedTransaction;
1✔
232
}
233

234
/**
235
 * Unwrap the supertoken (ERC777) into underlying asset (ERC20)
236
 * @param request the request that contains currency information
237
 * @param signer the web3 signer
238
 * @param amount to unwrap
239
 */
240
export async function unwrapSuperToken(
5✔
241
  request: ClientTypes.IRequestData,
242
  signer: Signer,
243
  amount: BigNumber,
244
): Promise<ContractTransaction> {
245
  const sf = await getSuperFluidFramework(request, signer.provider ?? getProvider());
1✔
246
  const superToken = await sf.loadSuperToken(request.currencyInfo.value);
1✔
247
  const userAddress = await signer.getAddress();
1✔
248
  const userBalance = await superToken.balanceOf({
1✔
249
    account: userAddress,
250
    providerOrSigner: signer.provider ?? getProvider(),
251
  });
252
  if (amount.gt(userBalance)) {
1✔
253
    throw new Error('Sender does not have enough supertoken');
×
254
  }
255
  const preparedTx = await prepareUnwrapSuperToken(
1✔
256
    request,
257
    signer.provider ?? getProvider(),
258
    amount,
259
  );
260
  return signer.sendTransaction(preparedTx);
1✔
261
}
262

263
/**
264
 * Check if there's an existing stream for the trio (Sender, Receiver, Currency)
265
 * @param sender address
266
 * @param receiver address
267
 * @param currency to stream
268
 * @param provider web3 provider.
269
 * @returns
270
 */
271
export async function checkExistingStream(
5✔
272
  request: ClientTypes.IRequestData,
273
  sender: string,
274
  provider: providers.Provider = getProvider(),
275
): Promise<boolean> {
276
  const id = getPaymentNetworkExtension(request)?.id;
×
277
  if (id !== ExtensionTypes.PAYMENT_NETWORK_ID.ERC777_STREAM) {
×
278
    throw new Error('Not a supported ERC777 payment network request');
×
279
  }
280
  validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC777_STREAM);
×
281
  const { paymentAddress } = getRequestPaymentValues(request);
×
282
  const sf = await getSuperFluidFramework(request, provider);
×
283
  const streams = await sf.query.listStreams({
×
284
    sender,
285
    receiver: paymentAddress,
286
    token: request.currencyInfo.value,
287
  });
288

289
  return streams.data.length > 0 && streams.data[0].currentFlowRate !== '0';
×
290
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc