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
909 changes: 909 additions & 0 deletions abis/ERC20CommerceEscrowWrapper.json

Large diffs are not rendered by default.

26 changes: 20 additions & 6 deletions cli/lib/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
erc20EscrowToPayArtifact,
erc20TransferableReceivableArtifact,
singleRequestForwarderFactoryArtifact,
erc20CommerceEscrowWrapperArtifact,
} from "@requestnetwork/smart-contracts";
import { EventFragment } from "@ethersproject/abi";
import { camelCase } from "lodash";
Expand All @@ -26,6 +27,7 @@ const paymentNetworks = {
ERC20EscrowToPay: erc20EscrowToPayArtifact,
ERC20TransferrableReceivable: erc20TransferableReceivableArtifact,
SingleRequestProxyFactory: singleRequestForwarderFactoryArtifact,
ERC20CommerceEscrowWrapper: erc20CommerceEscrowWrapperArtifact,
};

type DataSource = {
Expand All @@ -45,14 +47,14 @@ type DataSource = {

const getArtifactInfo = (artifact: ContractArtifact<any>, network: string) => {
return artifact
.getAllAddresses(network)
.filter((x) => Boolean(x.address))
.map(({ version }) => ({
...artifact.getDeploymentInformation(network, version),
.getAllAddresses(network as any)
.filter((x: any) => Boolean(x.address))
.map(({ version }: any) => ({
...artifact.getDeploymentInformation(network as any, version),
version,
}))
.filter(
(artifact, index, self) =>
(artifact: any, index: number, self: any[]) =>
self.findIndex((x) => x.address === artifact.address) === index,
);
};
Expand All @@ -70,6 +72,14 @@ const ignoredEvents = [
"ERC20FeeProxyUpdated",
];

// Contract-specific events to ignore
const contractSpecificIgnoredEvents: Record<string, string[]> = {
ERC20CommerceEscrowWrapper: [
"CommercePaymentAuthorized", // Redundant with PaymentAuthorized
"TransferWithReferenceAndFee", // Declared but never emitted by this contract (emitted by ERC20FeeProxy)
],
};

export const getManifest = (
TheGraphChainName: string,
RequestNetworkChainName: string,
Expand All @@ -82,6 +92,8 @@ export const getManifest = (
graphEntities = ["Payment", "Escrow", "EscrowEvent"];
} else if (pn === "SingleRequestProxyFactory") {
graphEntities = ["SingleRequestProxyDeployment"];
} else if (pn === "ERC20CommerceEscrowWrapper") {
graphEntities = ["Payment", "CommerceEscrow", "CommerceEscrowEvent"];
} else {
graphEntities = ["Payment"];
}
Expand All @@ -95,10 +107,12 @@ export const getManifest = (
);
});
infoArray.forEach(({ address, creationBlockNumber, version }) => {
const contractIgnoredEvents = contractSpecificIgnoredEvents[pn] || [];
const allIgnoredEvents = [...ignoredEvents, ...contractIgnoredEvents];
const events = artifact
.getContractAbi(version)
.filter((x) => x.type === "event")
.filter((x) => x.name && !ignoredEvents.includes(x.name))
.filter((x) => x.name && !allIgnoredEvents.includes(x.name))
.map((x) => ({
handlerName: "handle" + x.name,
eventSignature: EventFragment.fromObject(x)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
"remove-local": "graph remove --node http://localhost:8020/ --access-token \"\" requestnetwork/request-payments-$NETWORK",
"deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 requestnetwork/request-payments-$NETWORK ./subgraph.$NETWORK.yaml",
"format": "yarn prettier --write \"./src/**/*.ts\"",
"subgraph": "ts-node -r dotenv/config ./cli"
"subgraph": "ts-node -r dotenv/config ./cli/index.ts"
},
"dependencies": {
"@graphprotocol/graph-cli": "^0.81.0",
"@graphprotocol/graph-ts": "^0.35.1",
"@requestnetwork/smart-contracts": "0.45.1-next.0",
"@requestnetwork/smart-contracts": "0.49.0",
"graphql-request": "^3.5.0",
"ipfs-only-hash": "^4.0.0",
"lodash": "^4.17.21"
Expand Down
48 changes: 48 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,51 @@ type SingleRequestProxyDeployment @entity {
timestamp: Int!
txHash: Bytes!
}

enum CommerceEscrowState {
authorized
captured
voided
charged
reclaimed
refunded
}

type CommerceEscrow @entity {
id: ID!
contractAddress: Bytes!
reference: Bytes!
commercePaymentHash: Bytes!
creationBlock: Int!
creationTimestamp: Int!
escrowState: CommerceEscrowState!
tokenAddress: Bytes!
amount: BigDecimal!
payer: Bytes!
merchant: Bytes!
events: [CommerceEscrowEvent!] @derivedFrom(field: "escrow")
}

enum CommerceEscrowEventName {
authorized
captured
voided
charged
reclaimed
refunded
}

type CommerceEscrowEvent @entity {
id: ID!
contractAddress: Bytes!
reference: Bytes!
escrow: CommerceEscrow!
block: Int!
timestamp: Int!
txHash: Bytes!
eventName: CommerceEscrowEventName!
from: Bytes!
gasUsed: BigInt!
gasPrice: BigInt!
amount: BigDecimal
}
184 changes: 184 additions & 0 deletions src/erc20CommerceEscrowWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { Bytes, log } from "@graphprotocol/graph-ts";
import {
PaymentAuthorized,
PaymentCaptured,
PaymentCharged,
PaymentVoided,
PaymentReclaimed,
PaymentRefunded,
} from "../generated/ERC20CommerceEscrowWrapper/ERC20CommerceEscrowWrapper";
import { CommerceEscrow, CommerceEscrowEvent } from "../generated/schema";
import {
generateCommerceEscrowId,
generateCommerceEscrowEventId,
createCommerceEscrowEvent,
} from "./shared";

/**
* Handle the PaymentAuthorized event - creates a new commerce escrow
*/
export function handlePaymentAuthorized(event: PaymentAuthorized): void {
log.info("PaymentAuthorized at tx {} for reference {}", [
event.transaction.hash.toHexString(),
event.params.paymentReference.toHexString(),
]);

let paymentReference = Bytes.fromHexString(
event.params.paymentReference.toHexString(),
);
let escrowId = generateCommerceEscrowId(paymentReference);
let escrow = CommerceEscrow.load(escrowId);

if (escrow == null) {
escrow = new CommerceEscrow(escrowId);
escrow.contractAddress = event.address;
escrow.reference = paymentReference;
escrow.commercePaymentHash = event.params.commercePaymentHash;
escrow.creationBlock = event.block.number.toI32();
escrow.creationTimestamp = event.block.timestamp.toI32();
escrow.tokenAddress = event.params.token;
escrow.amount = event.params.amount.toBigDecimal();
escrow.payer = event.params.payer;
escrow.merchant = event.params.merchant;
escrow.escrowState = "authorized";
escrow.save();
}

createCommerceEscrowEvent(event, paymentReference, "authorized", null);
}

/**
* Handle the PaymentCaptured event - updates escrow state to captured
*/
export function handlePaymentCaptured(event: PaymentCaptured): void {
log.info("PaymentCaptured at tx {} for reference {}", [
event.transaction.hash.toHexString(),
event.params.paymentReference.toHexString(),
]);

let paymentReference = Bytes.fromHexString(
event.params.paymentReference.toHexString(),
);
updateCommerceEscrowState(paymentReference, "captured");
createCommerceEscrowEvent(
event,
paymentReference,
"captured",
event.params.capturedAmount.toBigDecimal(),
);
}

/**
* Handle the PaymentVoided event - updates escrow state to voided
*/
export function handlePaymentVoided(event: PaymentVoided): void {
log.info("PaymentVoided at tx {} for reference {}", [
event.transaction.hash.toHexString(),
event.params.paymentReference.toHexString(),
]);

let paymentReference = Bytes.fromHexString(
event.params.paymentReference.toHexString(),
);
updateCommerceEscrowState(paymentReference, "voided");
createCommerceEscrowEvent(
event,
paymentReference,
"voided",
event.params.voidedAmount.toBigDecimal(),
);
}

/**
* Handle the PaymentCharged event - creates a new commerce escrow in charged state
*/
export function handlePaymentCharged(event: PaymentCharged): void {
log.info("PaymentCharged at tx {} for reference {}", [
event.transaction.hash.toHexString(),
event.params.paymentReference.toHexString(),
]);

let paymentReference = Bytes.fromHexString(
event.params.paymentReference.toHexString(),
);
let escrowId = generateCommerceEscrowId(paymentReference);
let escrow = CommerceEscrow.load(escrowId);

if (escrow == null) {
escrow = new CommerceEscrow(escrowId);
escrow.contractAddress = event.address;
escrow.reference = paymentReference;
escrow.commercePaymentHash = event.params.commercePaymentHash;
escrow.creationBlock = event.block.number.toI32();
escrow.creationTimestamp = event.block.timestamp.toI32();
escrow.tokenAddress = event.params.token;
escrow.amount = event.params.amount.toBigDecimal();
escrow.payer = event.params.payer;
escrow.merchant = event.params.merchant;
}
escrow.escrowState = "charged";
escrow.save();

createCommerceEscrowEvent(
event,
paymentReference,
"charged",
event.params.amount.toBigDecimal(),
);
}

/**
* Handle the PaymentReclaimed event - updates escrow state to reclaimed
*/
export function handlePaymentReclaimed(event: PaymentReclaimed): void {
log.info("PaymentReclaimed at tx {} for reference {}", [
event.transaction.hash.toHexString(),
event.params.paymentReference.toHexString(),
]);

let paymentReference = Bytes.fromHexString(
event.params.paymentReference.toHexString(),
);
updateCommerceEscrowState(paymentReference, "reclaimed");
createCommerceEscrowEvent(
event,
paymentReference,
"reclaimed",
event.params.reclaimedAmount.toBigDecimal(),
);
}

/**
* Handle the PaymentRefunded event - updates escrow state to refunded
*/
export function handlePaymentRefunded(event: PaymentRefunded): void {
log.info("PaymentRefunded at tx {} for reference {}", [
event.transaction.hash.toHexString(),
event.params.paymentReference.toHexString(),
]);

let paymentReference = Bytes.fromHexString(
event.params.paymentReference.toHexString(),
);
updateCommerceEscrowState(paymentReference, "refunded");
createCommerceEscrowEvent(
event,
paymentReference,
"refunded",
event.params.refundedAmount.toBigDecimal(),
);
}

/**
* Helper function to update commerce escrow state
*/
function updateCommerceEscrowState(
paymentReference: Bytes,
eventType: string,
): void {
let escrow = CommerceEscrow.load(generateCommerceEscrowId(paymentReference));
if (!escrow) return;
escrow.escrowState = eventType;
escrow.save();
}

47 changes: 45 additions & 2 deletions src/shared.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ByteArray, Bytes, crypto, ethereum } from "@graphprotocol/graph-ts";
import { EscrowEvent } from "../generated/schema";
import {
BigDecimal,
ByteArray,
Bytes,
crypto,
ethereum,
} from "@graphprotocol/graph-ts";
import { EscrowEvent, CommerceEscrowEvent } from "../generated/schema";

export function generateId(
transaction: ethereum.Transaction,
Expand Down Expand Up @@ -33,3 +39,40 @@ export function createEscrowEvent(
escrowEvent.eventName = eventName;
escrowEvent.save();
}

// Commerce Escrow helper functions

export function generateCommerceEscrowId(paymentReference: Bytes): string {
return "commerce-" + paymentReference.toHex();
}

export function generateCommerceEscrowEventId(
transaction: ethereum.Transaction,
paymentReference: Bytes,
): string {
var id = transaction.hash.toHex() + paymentReference.toHex().slice(2);
return "commerce-" + crypto.keccak256(ByteArray.fromHexString(id)).toHex();
}

export function createCommerceEscrowEvent(
event: ethereum.Event,
paymentReference: Bytes,
eventName: string,
amount: BigDecimal | null,
): void {
let escrowEvent = new CommerceEscrowEvent(
generateCommerceEscrowEventId(event.transaction, paymentReference),
);
escrowEvent.reference = paymentReference;
escrowEvent.contractAddress = event.address;
escrowEvent.from = event.transaction.from;
escrowEvent.block = event.block.number.toI32();
escrowEvent.timestamp = event.block.timestamp.toI32();
escrowEvent.txHash = event.transaction.hash;
escrowEvent.gasUsed = (event.receipt as ethereum.TransactionReceipt).gasUsed;
escrowEvent.gasPrice = event.transaction.gasPrice;
escrowEvent.escrow = generateCommerceEscrowId(paymentReference);
escrowEvent.eventName = eventName;
escrowEvent.amount = amount;
escrowEvent.save();
}
Loading