This extension allows payments and refunds to be made in fungible tokens, including:
- ERC20 tokens on Ethereum and EVM-compatible blockchains.
- Fungible tokens on Near and Near testnet (as defined by NEP-141 and NEP-148)
This Payment Network is similar to the ERC20 Proxy Contract extension, with the added feature of allowing a fee to be taken from the payment.
The payment is mainly expected through a proxy payment contract, but the request issuer can also declare payments manually. Fees shall not be paid for declarative payments.
The proxy contract does the fungible token transfer on behalf of the user. The contract ensures a link between a token transfer and a request through a paymentReference
. This paymentReference
consists of the last 8 bytes of a salted hash of the requestId: last8Bytes(hash(lowercase(requestId + salt + address)))
:
The contract also ensures that the feeAmount
amount of the token transfer will be forwarded to the feeAddress
.
requestId
is the id of the requestsalt
is a random number with at least 8 bytes of randomness. It must be unique to each requestaddress
is the payment address for payments, the refund address for refundsfeeAmount
is the amount of the transfer that should be paid in feesfeeAddress
is the address where the fee will be sent tolowercase()
transforms all characters to lowercasehash()
is a keccak256 hash functionlast8Bytes()
take the last 8 bytes
As a payment network, this extension allows to deduce a payment balance
for the request. (see Interpretation)
On EVMs, the contract contains one function called transferFromWithReferenceAndFee
which takes 6 arguments:
tokenAddress
is the address of the ERC20 contractto
is the destination address for the tokensamount
is the amount of tokens to transfer to the destination addresspaymentReference
is the reference data used to track the transfer (seepaymentReference
)feeAmount
is the amount of tokens to transfer to the fee destination addressfeeAddress
is the destination address for the fee
On Near, users send fungible tokens to the contract with the ft_transfer_call
method, if the msg
value given is a valid JSON object with 4 of the 6 arguments listed above: to
, paymentReference
, feeAmount
and feeAddress
. The tokenAdress
is taken from the calling fungible token contract. The amount
is equal to the transfer (total) amount
less feeAmount
.
On EVM-compatible chains, the TransferWithReferenceAndFee
event is emitted when the tokens are transfered. This event contains the same 6 arguments as the transferFromWithReferenceAndFee
function.
On Near and Near testnet, a JSON message is logged by the method on_transfer_with_reference
, containing the same 6 arguments.
See EVM smart contract source See Near smart contract source
Network | Contract Address |
---|---|
Ethereum Mainnet | 0x370DE27fdb7D1Ff1e1BaA7D11c5820a324Cf623C |
Matic | 0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9 |
Celo | 0x2171a0dc12a9E5b1659feF2BB20E54c84Fa7dB0C |
Ethereum Testnet - Rinkeby | 0xda46309973bffddd5a10ce12c44d2ee266f45a44 |
Matic Testnet - Mumbai | 0x131eb294E3803F23dc2882AB795631A12D1d8929 |
Private | 0x75c35C980C0d37ef46DF04d31A140b65503c0eEd |
Near Testnet | pay.reqnetwork.testnet |
The updated list of deployment address can be found in the smart-contracts package;
The issuer can declare that he received a payment and give the amount, possibly with a txHash
and network
for documentation.
Property | Type | Description | Requirement |
---|---|---|---|
id | String | constant value: "pn-erc20-fee-proxy-contract" | Mandatory |
type | String | constant value: "paymentNetwork" | Mandatory |
version | String | constant value: "0.1.0" | Mandatory |
events | Array | List of the actions performed by the extension | Mandatory |
values | Object | ||
values.salt | String | Salt for the request | Mandatory |
values.paymentAddress | String | Blockchain address for the payment | Optional |
values.refundAddress | String | Blockchain address for the refund | Optional |
values.feeAddress | String | Blockchain address for the fee payment | Optional |
values.feeAmount | String | The fee amount in the request currency |
Optional |
Type | Description | Requirement | |
---|---|---|---|
id | String | Constant value: "pn-erc20-fee-proxy-contract" | Mandatory |
type | String | Constant value: "paymentNetwork" | Mandatory |
version | String | Constant value: "0.1.0" | Mandatory |
parameters | Object | ||
parameters.salt | String | Salt for the request | Mandatory |
parameters.paymentAddress | String | Blockchain address for the payment | Optional |
parameters.refundAddress | String | Blockchain address for the refund | Optional |
parameters.feeAddress | String | Blockchain address for the fee payment | Optional |
parameters.feeAmount | String | The fee amount in the request currency |
Optional |
This action is valid if:
- The
salt
is not empty and long enough (8 bytes of randomness minimum). - The
currency.type
is ERC20.
This action must trigger the warnings:
Warning | Condition |
---|---|
"paymentAddress is given by the payer" | If signer is the payer and paymentAddress is given |
"feeAddress is given by the payer" | If signer is the payer and feeAddress is given |
"feeAmount is given by the payer" | If signer is the payer and feeAddress is given |
"refundAddress is given by the payee" | If signer is the payee and refundAddress is given |
Note: These warnings are necessary to highlight and avoid attempts of fake payments and refunds. For example, a payer could create a request using as the payment address one of his own addresses. A system could interpret a transaction to this address as a payment while the payee did not receive the funds.
The extension state is created with the following properties:
Property | Value |
---|---|
id | "pn-erc20-fee-proxy-contract" |
type | "paymentNetwork" |
version | "0.1.0" |
values | |
values.paymentAddress | paymentAddress from parameters if given, undefined otherwise |
values.refundAddress | refundAddress from parameters if given, undefined otherwise |
values.feeAddress | feeAddress from parameters if given, undefined otherwise |
values.feeAmount | feeAmount from parameters if given, undefined otherwise |
values.salt | Salt for the request |
events | Array with one 'create' event (see below) |
the 'create' event:
Property | Value |
---|---|
name | 'create' |
parameters | |
parameters.paymentAddress | paymentAddress from parameters if given, undefined otherwise |
parameters.refundAddress | refundAddress from parameters if given, undefined otherwise |
parameters.feeAddress | feeAddress from parameters if given, undefined otherwise |
parameters.feeAmount | feeAmount from parameters if given, undefined otherwise |
parameters.salt | Salt for the request |
Type | Description | Requirement | |
---|---|---|---|
id | String | Constant value: "pn-erc20-fee-proxy-contract" | Mandatory |
action | String | Constant value: "addPaymentAddress" | Mandatory |
parameters | Object | ||
parameters.paymentAddress | String | Blockchain address for the payment | Mandatory |
This action is valid, if:
- The extension state with the id "pn-erc20-fee-proxy-contract" exists
- The signer is the
payee
- The extension property
paymentAddress
is undefined
None.
An extension state is updated with the following properties:
Property | Value |
---|---|
values.paymentAddress | paymentAddress from parameters |
events | Add an 'paymentAddress' event (see below) at its end |
the 'addPaymentAddress' event:
Property | Value |
---|---|
name | Constant value: "addPaymentAddress" |
parameters | |
parameters.paymentAddress | paymentAddress from parameters |
Type | Description | Requirement | |
---|---|---|---|
id | String | Constant value: "pn-erc20-fee-proxy-contract" | Mandatory |
action | String | Constant value: "addRefundAddress" | Mandatory |
parameters | Object | ||
parameters.refundAddress | String | Blockchain address for the refund | Mandatory |
This action is valid if:
- The extension state with the id "pn-erc20-fee-proxy-contract" exists
- The signer is the
payer
- The extension property
refundAddress
is undefined
None.
The extension state is updated with the following properties:
Property | Value |
---|---|
values.refundAddress | refundAddress from parameters |
events | Add an 'addRefundAddress' event (see below) at its end |
The 'addRefundAddress' event:
Property | Value |
---|---|
name | 'addRefundAddress' |
parameters | |
parameters.refundAddress | refundAddress from parameters |
Type | Description | Requirement | |
---|---|---|---|
id | String | Constant value: "pn-erc20-fee-proxy-contract" | Mandatory |
action | String | Constant value: "addFeeAddress" | Mandatory |
parameters | Object | ||
parameters.feeAddress | String | Blockchain address for the fee payment | Mandatory |
parameters.feeAmount | String | The fee amount | Mandatory |
This action is valid, if:
- The extension state with the id "pn-erc20-fee-proxy-contract" exists
- The signer is the
payee
- The extension property
feeAddress
is undefined - The extension property
feeAmount
is undefined or represents an integer greater or equal than zero
None.
The extension state is updated with the following properties:
Property | Value |
---|---|
values.feeAddress | feeAddress from parameters |
values.feeAmount | feeAmount from parameters |
events | Add a 'fee' event (see below) at its end |
the 'addFee' event:
Property | Value |
---|---|
name | Constant value: "addFeeAddress" |
parameters | |
parameters.feeAddress | feeAddress from parameters |
parameters.feeAmount | feeAmount from parameters |
Type | Description | Requirement | |
---|---|---|---|
id | String | Constant value: "pn-erc20-fee-proxy-contract" | Mandatory |
action | String | Constant value: "declareReceivedPayment" | Mandatory |
parameters | Object | ||
parameters.amount | Amount | The amount declared as received, in request currency | Mandatory |
parameters.note | String | Additional information about the payment | Optional |
parameters.txHash | String | The transaction hash for documentation and metadata | Optional |
parameters.network | String | The network of the transaction for documentation and metadata | Optional |
This action is valid, if:
- The extension state with the id "pn-erc20-fee-proxy-contract" exists
- The signer is the
payee
None.
An event is added to the extension state events array:
Property | Value |
---|---|
name | Constant value: "declareReceivedPayment" |
parameters | |
parameters.amount | amount from parameters |
parameters.note | note from parameters |
parameters.txHash | txHash from parameters or undefined |
Type | Description | Requirement | |
---|---|---|---|
id | String | Constant value: "pn-erc20-fee-proxy-contract" | Mandatory |
action | String | Constant value: "declareReceivedRefund" | Mandatory |
parameters | Object | ||
parameters.amount | Amount | The amount declared as received, in request currency | Mandatory |
parameters.note | String | Additional information about the payment | Optional |
parameters.txHash | String | The transaction hash for documentation and metadata | Optional |
parameters.network | String | The network of the transaction for documentation and metadata | Optional |
This action is valid, if:
- The extension state with the id "pn-erc20-fee-proxy-contract" exists
- The signer is the
payee
None.
An event is added to the extension state events array:
Property | Value |
---|---|
name | Constant value: "declareReceivedRefund" |
parameters | |
parameters.amount | amount from parameters |
parameters.note | note from parameters |
parameters.txHash | txHash from parameters or undefined |
parameters.network | network from parameters or undefined |
The fee proxy contract address is determined by the request.currency.network
(see (table)[#Contract] with proxy contract addresses).
Any TransferWithReferenceAndFee
events emitted from the proxy contract with the following arguments are considered as a payment:
tokenAddress === request.currency.value
to === paymentAddress
paymentReference === last8Bytes(hash(lowercase(requestId + salt + payment address)))
Any declareReceivedPayment
event is considered a payment.
Any declareReceivedRefund
event is considered a refund.
Any TransferWithReferenceAndFee
events emitted from the proxy contract with the following arguments are considered as a refund:
tokenAddress === request.currency.value
to === refundAddress
paymentReference === last8Bytes(hash(lowercase(requestId + salt + refund address)))
The sum of payment amounts minus the sum of refund amounts is considered the balance.
The fees amount can be be infered from the TransferWithReferenceAndFee
events emitted from the proxy contract.