Skip to content

Commit

Permalink
Payments - Receive on chain payments for off chain items (#13)
Browse files Browse the repository at this point in the history
* Add payments

* Payment can have multiple payment recipients

* Add payments to deployables

* Payments hashing on contract. Includes chainid

* Allow zero payments

* payment accepted in interface

* Allow payment chaining

* Update gas snapshot

* Add ability for signer make chained call

* Additional payments tests

* Update gas snapshot

* Test stability

* Add signer to payments interface

* Parity for chained calls

* Format

* Support contract signers for Payments

* Add Factory for payments
  • Loading branch information
ScreamingHawk authored Oct 8, 2024
1 parent eb3a86d commit cc6b1d3
Show file tree
Hide file tree
Showing 11 changed files with 1,241 additions and 103 deletions.
234 changes: 136 additions & 98 deletions .gas-snapshot

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import util from 'util'
import {
BUILD_DIR,
DEPLOYABLE_CONTRACT_NAMES,
TOKEN_CONTRACT_NAMES,
PROXIED_TOKEN_CONTRACT_NAMES,
} from './constants'
const exec = util.promisify(execNonPromise)

Expand All @@ -27,7 +27,7 @@ const main = async () => {
// Create the compiler input files
for (const solFile of [
...DEPLOYABLE_CONTRACT_NAMES,
...TOKEN_CONTRACT_NAMES,
...PROXIED_TOKEN_CONTRACT_NAMES,
'TransparentUpgradeableBeaconProxy',
'UpgradeableBeacon',
]) {
Expand Down
4 changes: 3 additions & 1 deletion scripts/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ export const DEPLOYABLE_CONTRACT_NAMES = [
'ERC1155SaleFactory',
'ERC1155SoulboundFactory',
'PaymentCombiner',
'PaymentsFactory',
'Clawback',
'ClawbackMetadata',
]
export const TOKEN_CONTRACT_NAMES = [
export const PROXIED_TOKEN_CONTRACT_NAMES = [
'ERC20Items',
'ERC721Items',
'ERC721Sale',
'ERC721Soulbound',
'ERC1155Items',
'ERC1155Sale',
'ERC1155Soulbound',
'Payments',
]
4 changes: 2 additions & 2 deletions scripts/outputSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TOKEN_CONTRACT_NAMES } from './constants'
import { PROXIED_TOKEN_CONTRACT_NAMES } from './constants'

const { spawn } = require('child_process')

Expand Down Expand Up @@ -31,4 +31,4 @@ const outputSelectors = (contractName: string) => {
})
}

TOKEN_CONTRACT_NAMES.forEach(outputSelectors)
PROXIED_TOKEN_CONTRACT_NAMES.forEach(outputSelectors)
139 changes: 139 additions & 0 deletions src/payments/IPayments.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

interface IPaymentsFunctions {
enum TokenType {
ERC20,
ERC721,
ERC1155
}

struct PaymentRecipient {
// Payment recipient
address recipient;
// Payment amount
uint256 amount;
}

struct ChainedCallDetails {
// Address for chained call
address chainedCallAddress;
// Data for chained call
bytes chainedCallData;
}

struct PaymentDetails {
// Unique ID for this purchase
uint256 purchaseId;
// Recipient of the purchased product
address productRecipient;
// Type of payment token
TokenType tokenType;
// Token address to use for payment
address tokenAddress;
// Token ID to use for payment. Used for ERC-721 and 1155 payments
uint256 tokenId;
// Payment receipients
PaymentRecipient[] paymentRecipients;
// Expiration time of the payment
uint64 expiration;
// ID of the product
string productId;
// Chained call details
ChainedCallDetails chainedCallDetails;
}

/**
* Returns the hash of the payment details.
* @param paymentDetails The payment details.
* @return paymentHash The hash of the payment details for signing.
*/
function hashPaymentDetails(PaymentDetails calldata paymentDetails) external view returns (bytes32 paymentHash);

/**
* Check is a payment signature is valid.
* @param paymentDetails The payment details.
* @param signature The signature of the payment.
* @return isValid True if the signature is valid.
*/
function isValidPaymentSignature(PaymentDetails calldata paymentDetails, bytes calldata signature)
external
view
returns (bool isValid);

/**
* Make a payment for a product.
* @param paymentDetails The payment details.
* @param signature The signature of the payment.
*/
function makePayment(PaymentDetails calldata paymentDetails, bytes calldata signature) external payable;

/**
* Check if a payment has been accepted.
* @param purchaseId The ID of the purchase.
* @return accepted True if the payment has been accepted.
*/
function paymentAccepted(uint256 purchaseId) external view returns (bool);

/**
* Returns the hash of the chained call.
* @param chainedCallDetails The chained call details.
* @return callHash The hash of the chained call for signing.
*/
function hashChainedCallDetails(ChainedCallDetails calldata chainedCallDetails)
external
view
returns (bytes32 callHash);

/**
* Complete a chained call.
* @param chainedCallDetails The chained call details.
* @param signature The signature of the chained call.
* @dev This is called when a payment is accepted off/cross chain.
*/
function performChainedCall(ChainedCallDetails calldata chainedCallDetails, bytes calldata signature) external;

/**
* Check is a chained call signature is valid.
* @param chainedCallDetails The chained call details.
* @param signature The signature of the chained call.
* @return isValid True if the signature is valid.
*/
function isValidChainedCallSignature(ChainedCallDetails calldata chainedCallDetails, bytes calldata signature)
external
view
returns (bool isValid);

/**
* Get the signer address.
* @return signer The signer address.
*/
function signer() external view returns (address);
}

interface IPaymentsSignals {
/// @notice Emitted when contract is already initialized.
error InvalidInitialization();

/// @notice Emitted when a payment is already accepted. This prevents double spending.
error PaymentAlreadyAccepted();

/// @notice Emitted when a signature is invalid.
error InvalidSignature();

/// @notice Emitted when a payment has expired.
error PaymentExpired();

/// @notice Emitted when a token transfer is invalid.
error InvalidTokenTransfer();

/// @notice Emitted when a chained call fails.
error ChainedCallFailed();

/// @notice Emitted when a payment is made.
event PaymentMade(
address indexed spender, address indexed productRecipient, uint256 indexed purchaseId, string productId
);
}

interface IPayments is IPaymentsFunctions, IPaymentsSignals {}
36 changes: 36 additions & 0 deletions src/payments/IPaymentsFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

interface IPaymentsFactoryFunctions {
/**
* Creates a Payments proxy contract
* @param proxyOwner The owner of the Payments proxy
* @param paymentsOwner The owner of the Payments implementation
* @param paymentsSigner The signer of the Payments implementation
* @return proxyAddr The address of the Payments proxy
*/
function deploy(address proxyOwner, address paymentsOwner, address paymentsSigner)
external
returns (address proxyAddr);

/**
* Computes the address of a proxy instance.
* @param proxyOwner The owner of the Payments proxy
* @param paymentsOwner The owner of the Payments implementation
* @param paymentsSigner The signer of the Payments implementation
* @return proxyAddr The address of the Payments proxy
*/
function determineAddress(address proxyOwner, address paymentsOwner, address paymentsSigner)
external
returns (address proxyAddr);
}

interface IPaymentsFactorySignals {
/**
* Event emitted when a new Payments proxy contract is deployed.
* @param proxyAddr The address of the deployed proxy.
*/
event PaymentsDeployed(address proxyAddr);
}

interface IPaymentsFactory is IPaymentsFactoryFunctions, IPaymentsFactorySignals {}
Loading

0 comments on commit cc6b1d3

Please sign in to comment.