Skip to content
This repository was archived by the owner on Dec 11, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
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
80 changes: 80 additions & 0 deletions projects/boson/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const protocolDiamondAddress = "0x59A4C19b55193D5a2EAD0065c54af4d516E18Cb5";

const protocolDeploymentBlock = {
polygon: 34258150,
ethereum: 18240548,
base: 25765296,
arbitrum: 321778899,
optimism: 133002790,
};

/** Event topics **/
const fundsEncumberedTopic = "0x8080d30eb13935d67dfdc606fa5e4170aa03ffdfaf40136ef3fa4355c88b19f9";
const fundsReleasedTopic = "0xea32da6ba3310a019ad2eba7e0b3063c1dbe129dbc3bfa2638b5d3165183e6ae";
const protocolFeeCollectedTopic = "0x9399ed37d26489264206ddf87ad81aaa13557b4e56b5443503a2b64a24ec42db";

const offerCreatedTopic_v2_0_0 = "0x845c99b8425be384387e239b85d44b6cdf63aab6c45c2534f799743e341c74f8";
const offerCreatedTopic_v2_3_0 = "0xb6a507882e43ec6bae2f276ce4a839f1ca9dcf2da73095eea9e633e96ecf6eb2";
const offerCreatedTopic_v2_4_0 = "0xa76af238b31b285c9397e2a7c55650ca18a1baf6504de15d7dd1321693144cf7";
const offerCreatedTopic_v2_5_0 = "0x5235a4f9db2e479e7353e22e7629a4556897ddaf0ab4eaeb9bb8660c8e49a903";
const offerVoidedTopic = "0xe7c7f2b8b4a6597a3aa617aff2d206ea5507c118fb6d4e54e130279510796fd5";
const rangeReservedTopic = "0xede05ffdb8c59f6f9e12923efcf03736da40cda9a4c5ba60ac2b6238b8b86a60";

const buyerCommittedTopic_v2_0_0 = "0x442279a0d0683a12971990518f9f3f874391650139a762c4e94b23b51f04d94f";
const buyerCommittedTopic_v2_5_0 = "0xb52015a0ea2e0345baee81bf183c410328b8f8d0cfd380c43006bb84b78fc100";

/** Event definitions **/
/**
* Some event changed over time, but kept their name, so we define multiple versions here
*/
const OfferCreatedEvent_v2_0_0 =
"event OfferCreated(uint256 indexed offerId, uint256 indexed sellerId, (uint256 id, uint256 sellerId, uint256 price, uint256 sellerDeposit, uint256 buyerCancelPenalty, uint256 quantityAvailable, address exchangeToken, string metadataUri, string metadataHash, bool voided) offer, (uint256 validFrom, uint256 validUntil, uint256 voucherRedeemableFrom, uint256 voucherRedeemableUntil) offerDates, (uint256 disputePeriod, uint256 voucherValid, uint256 resolutionPeriod) offerDurations, (uint256 disputeResolverId, uint256 escalationResponsePeriod, uint256 feeAmount, uint256 buyerEscalationDeposit) disputeResolutionTerms, (uint256 protocolFee, uint256 agentFee) offerFees, uint256 indexed agentId, address executedBy)";
const OfferCreatedEvent_v2_3_0 =
"event OfferCreated(uint256 indexed offerId, uint256 indexed sellerId, (uint256 id, uint256 sellerId, uint256 price, uint256 sellerDeposit, uint256 buyerCancelPenalty, uint256 quantityAvailable, address exchangeToken, string metadataUri, string metadataHash, bool voided, uint256 collectionIndex) offer, (uint256 validFrom, uint256 validUntil, uint256 voucherRedeemableFrom, uint256 voucherRedeemableUntil) offerDates, (uint256 disputePeriod, uint256 voucherValid, uint256 resolutionPeriod) offerDurations, (uint256 disputeResolverId, uint256 escalationResponsePeriod, uint256 feeAmount, uint256 buyerEscalationDeposit) disputeResolutionTerms, (uint256 protocolFee, uint256 agentFee) offerFees, uint256 indexed agentId, address executedBy)";
const OfferCreatedEvent_v2_4_0 =
"event OfferCreated(uint256 indexed offerId, uint256 indexed sellerId, (uint256 id, uint256 sellerId, uint256 price, uint256 sellerDeposit, uint256 buyerCancelPenalty, uint256 quantityAvailable, address exchangeToken, uint8 priceType, string metadataUri, string metadataHash, bool voided, uint256 collectionIndex, (address[] recipients, uint256[] bps)[] royaltyInfo) offer, (uint256 validFrom, uint256 validUntil, uint256 voucherRedeemableFrom, uint256 voucherRedeemableUntil) offerDates, (uint256 disputePeriod, uint256 voucherValid, uint256 resolutionPeriod) offerDurations, (uint256 disputeResolverId, uint256 escalationResponsePeriod, uint256 feeAmount, uint256 buyerEscalationDeposit) disputeResolutionTerms, (uint256 protocolFee, uint256 agentFee) offerFees, uint256 indexed agentId, address executedBy)";
const OfferCreatedEvent_v2_5_0 =
"event OfferCreated(uint256 indexed offerId, uint256 indexed sellerId, (uint256 id, uint256 sellerId, uint256 price, uint256 sellerDeposit, uint256 buyerCancelPenalty, uint256 quantityAvailable, address exchangeToken, uint8 priceType, uint8 creator, string metadataUri, string metadataHash, bool voided, uint256 collectionIndex, (address[] recipients, uint256[] bps)[] royaltyInfo, uint256 buyerId) offer, (uint256 validFrom, uint256 validUntil, uint256 voucherRedeemableFrom, uint256 voucherRedeemableUntil) offerDates, (uint256 disputePeriod, uint256 voucherValid, uint256 resolutionPeriod) offerDurations, (uint256 disputeResolverId, uint256 escalationResponsePeriod, uint256 feeAmount, uint256 buyerEscalationDeposit, address mutualizerAddress) disputeResolutionTerms, (uint256 protocolFee, uint256 agentFee) offerFees, uint256 indexed agentId, address executedBy)";
const OfferVoidedEvent =
"event OfferVoided(uint256 indexed offerId, uint256 indexed creatorId, address indexed executedBy)";
const RangeReservedEvent =
"event RangeReserved(uint256 indexed offerId, uint256 indexed sellerId, uint256 startExchangeId, uint256 endExchangeId, address owner, address indexed executedBy)";

const FundsEncumberedEvent =
"event FundsEncumbered(uint256 indexed entityId, address indexed exchangeToken, uint256 amount, address indexed executedBy)";
const FundsReleasedEvent =
"event FundsReleased(uint256 indexed exchangeId, uint256 indexed entityId, address indexed exchangeToken, uint256 amount, address executedBy)";
const ProtocolFeeCollectedEvent =
"event ProtocolFeeCollected(uint256 indexed exchangeId, address indexed exchangeToken, uint256 amount, address indexed executedBy)";

const BuyerCommittedEvent_v2_0_0 =
"event BuyerCommitted(uint256 indexed offerId, uint256 indexed buyerId, uint256 indexed exchangeId, (uint256 id, uint256 offerId, uint256 buyerId, uint256 finalizedDate, uint8 state) exchange, (uint256 committedDate, uint256 validUntilDate, uint256 redeemedDate, bool expired) voucher, address executedBy)";
const BuyerCommittedEvent_v2_5_0 =
"event BuyerCommitted(uint256 indexed offerId, uint256 indexed buyerId, uint256 indexed exchangeId, (uint256 id, uint256 offerId, uint256 buyerId, uint256 finalizedDate, uint8 state, address mutualizerAddress) exchange, (uint256 committedDate, uint256 validUntilDate, uint256 redeemedDate, bool expired) voucher, address executedBy)";

module.exports = {
protocolDiamondAddress,
protocolDeploymentBlock,
fundsEncumberedTopic,
fundsReleasedTopic,
protocolFeeCollectedTopic,
offerCreatedTopic_v2_0_0,
offerCreatedTopic_v2_3_0,
offerCreatedTopic_v2_4_0,
offerCreatedTopic_v2_5_0,
offerVoidedTopic,
rangeReservedTopic,
buyerCommittedTopic_v2_0_0,
buyerCommittedTopic_v2_5_0,
OfferCreatedEvent_v2_0_0,
OfferCreatedEvent_v2_3_0,
OfferCreatedEvent_v2_4_0,
OfferCreatedEvent_v2_5_0,
OfferVoidedEvent,
RangeReservedEvent,
FundsEncumberedEvent,
FundsReleasedEvent,
ProtocolFeeCollectedEvent,
BuyerCommittedEvent_v2_0_0,
BuyerCommittedEvent_v2_5_0,
};
114 changes: 103 additions & 11 deletions projects/boson/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,109 @@
const { staking } = require("../helper/staking");
const { MaxUint256 } = require("ethers");

const {
getVoidedOffers,
getOffers,
getCommits,
getReservedRanges,
getEncumberedFunds,
} = require("./utils");

async function tvl(api) {
// Currently Encumbered funds.
// Includes all buyer commitments, seller deposits and dispute resolution fees for active exchanges for both Fixed Price and Price Discovery offers.
// It also includes all additional commits as result of Sequential Commitments
const tokenBalances = await getEncumberedFunds(api);

// Listed offers with uncommitted quantity
const voidedOffers = await getVoidedOffers(api);
console.log("#of voided offers:", voidedOffers.length);

const activeOffers = await getOffers(api, voidedOffers);
console.log("#of active offers:", activeOffers.length);

// Ignore Price Discovery offers. Their price is unknown until commitment. Its contribution to TVL is included in `getEncumberedFunds`.
// Work only with Fixed Price offers from here on.
const fixedPriceOffers = activeOffers.filter((i) => i.offer?.priceType !== 1n); // priceType 1 = Price Discovery
console.log("#of fixed price offers:", fixedPriceOffers.length);

// Get the existing commits to avoid double counting
const commitsByOffer = await getCommits(api);

// Handle first the offers with upfront known quantityAvailable
const limitedOffers = fixedPriceOffers.filter((i) => i.offer?.quantityAvailable !== MaxUint256); // MaxUint256 indicates unlimited quantityAvailable
for (const offer of limitedOffers) {
const offerId = BigInt(offer.offerId);
// Uncommitted quantity is total quantityAvailable minus already committed quantity
// No need to check for reserved ranges since they do not affect the quantityAvailable
const uncommittedQuantity = BigInt(offer.offer.quantityAvailable) - BigInt(commitsByOffer[offerId]?.length || 0);

addUncommittedQuantityToTokenBalances(offer, uncommittedQuantity, tokenBalances);
}

// Handle the offers with unlimited quantityAvailable
// Their uncommitted quantity is determined by the reserved range minus the number of commits that fall within its reserved range
// If there are any additional commits outside the reserved range, belonging to the same offer, they are included in TVL in `getEncumberedFunds` already
const unlimitedOffers = fixedPriceOffers.filter((i) => i.offer?.quantityAvailable === MaxUint256);
const reservedRanges = await getReservedRanges(api);
for (const offer of unlimitedOffers) {
const offerId = BigInt(offer.offerId);
const reservedRange = reservedRanges[offerId];
if (reservedRange) {
const { startExchangeId, endExchangeId } = reservedRange;
// Check if any of the commits fall within the reserved range
// Range is inclusive of both startExchangeId and endExchangeId
const commitsInRange =
commitsByOffer[offerId]?.filter((exchangeId) => {
const exId = BigInt(exchangeId);
return exId >= startExchangeId && exId <= endExchangeId;
}).length || 0n;
const uncommittedQuantity = endExchangeId - startExchangeId + 1n - commitsInRange;

addUncommittedQuantityToTokenBalances(offer, uncommittedQuantity, tokenBalances);
}
}

console.table(tokenBalances);

api.addTokens(Object.keys(tokenBalances), Object.values(tokenBalances));
}

function addUncommittedQuantityToTokenBalances(offer, uncommittedQuantity, tokenBalances) {
const exchangeToken = offer.offer.exchangeToken;
const price = BigInt(offer.offer.price);
const sellerDeposit = BigInt(offer.offer.sellerDeposit);
const drFeeAmount = BigInt(offer.disputeResolutionTerms.feeAmount);
const lockedPerExchange = price + sellerDeposit + drFeeAmount;
const totalLocked = lockedPerExchange * uncommittedQuantity;

tokenBalances[exchangeToken] = (tokenBalances[exchangeToken] || 0n) + totalLocked;
}

module.exports = {
ethereum: {
tvl: () => ({}),
staking: staking(
[
"0x6244bc0d4b661526c0c62c3610571cd1ac9df2dd",
"0xbacc083795846a67b0782327a96622447ddafe6c",
"0x081a52f02e51978ad419dd7894d7ae3555f8bc26",
"0x3ed0c99c8e8eb94438837cc8a08ca3bb187424cf",
"0x3810d9d6685812af6ef4257de0542ecdba9bfd95"
],
"0xC477D038d5420C6A9e0b031712f61c5120090de9"
),
tvl,
// staking: staking(
// [
// "0x6244bc0d4b661526c0c62c3610571cd1ac9df2dd",
// "0xbacc083795846a67b0782327a96622447ddafe6c",
// "0x081a52f02e51978ad419dd7894d7ae3555f8bc26",
// "0x3ed0c99c8e8eb94438837cc8a08ca3bb187424cf",
// "0x3810d9d6685812af6ef4257de0542ecdba9bfd95"
// ],
// "0xC477D038d5420C6A9e0b031712f61c5120090de9"
// ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// staking: staking(
// [
// "0x6244bc0d4b661526c0c62c3610571cd1ac9df2dd",
// "0xbacc083795846a67b0782327a96622447ddafe6c",
// "0x081a52f02e51978ad419dd7894d7ae3555f8bc26",
// "0x3ed0c99c8e8eb94438837cc8a08ca3bb187424cf",
// "0x3810d9d6685812af6ef4257de0542ecdba9bfd95"
// ],
// "0xC477D038d5420C6A9e0b031712f61c5120090de9"
// ),

},
polygon: {
tvl,
},
base: {
tvl,
},
optimism: {
tvl,
},
arbitrum: {
tvl,
},
};
179 changes: 179 additions & 0 deletions projects/boson/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
const ethers = require("ethers");
const { getLogs } = require("../helper/cache/getLogs");

const {
protocolDiamondAddress,
protocolDeploymentBlock,
fundsEncumberedTopic,
fundsReleasedTopic,
protocolFeeCollectedTopic,
offerCreatedTopic_v2_0_0,
offerCreatedTopic_v2_3_0,
offerCreatedTopic_v2_4_0,
offerCreatedTopic_v2_5_0,
offerVoidedTopic,
rangeReservedTopic,
buyerCommittedTopic_v2_0_0,
buyerCommittedTopic_v2_5_0,
OfferCreatedEvent_v2_0_0,
OfferCreatedEvent_v2_3_0,
OfferCreatedEvent_v2_4_0,
OfferCreatedEvent_v2_5_0,
OfferVoidedEvent,
RangeReservedEvent,
FundsEncumberedEvent,
FundsReleasedEvent,
ProtocolFeeCollectedEvent,
BuyerCommittedEvent_v2_0_0,
BuyerCommittedEvent_v2_5_0,
} = require("./constants");

async function getVoidedOffers(api) {
const logs = await getLogs({
api,
target: protocolDiamondAddress,
topics: [offerVoidedTopic],
fromBlock: protocolDeploymentBlock[api.chain],
eventAbi: OfferVoidedEvent,
onlyArgs: true,
extraKey: "offerVoided",
});

return logs.map((i) => BigInt(i.offerId));
}

async function getOffers(api, voidedOffers = []) {
const response = await getLogs({
api,
target: protocolDiamondAddress,
topics: [[offerCreatedTopic_v2_0_0, offerCreatedTopic_v2_3_0, offerCreatedTopic_v2_4_0, offerCreatedTopic_v2_5_0]],
fromBlock: protocolDeploymentBlock[api.chain],
extraKey: "offerCreated",
});

// Combine all versions of the OfferCreated event
const iface = new ethers.Interface([
OfferCreatedEvent_v2_0_0,
OfferCreatedEvent_v2_3_0,
OfferCreatedEvent_v2_4_0,
OfferCreatedEvent_v2_5_0,
]);

const parsedLogs = response.map((log) => {
return iface.parseLog(log)?.args;
});

console.log("Total offers fetched:", parsedLogs.length);

const blockTimestamp = BigInt(api.timestamp);

return parsedLogs.filter(
(i) =>
!voidedOffers.includes(BigInt(i.offerId)) &&
BigInt(i?.offerDates?.validUntil) >= blockTimestamp &&
BigInt(i?.offerDates?.validFrom) < blockTimestamp
);
}

async function getCommits(api) {
const response = await getLogs({
api,
target: protocolDiamondAddress,
topics: [[buyerCommittedTopic_v2_0_0, buyerCommittedTopic_v2_5_0]],
fromBlock: protocolDeploymentBlock[api.chain],
extraKey: "buyerCommitted",
});

// Combine all versions of the BuyerCommitted event
const iface = new ethers.Interface([BuyerCommittedEvent_v2_0_0, BuyerCommittedEvent_v2_5_0]);

const parsedLogs = response.map((log) => {
return iface.parseLog(log)?.args;
});

console.log("Total # of commits:", parsedLogs.length);

const commitsByOffer = {};

for (const commit of parsedLogs) {
const offerId = BigInt(commit.offerId);
if (!commitsByOffer[offerId]) {
commitsByOffer[offerId] = [commit.exchangeId];
} else {
commitsByOffer[offerId].push(commit.exchangeId);
}
}

return commitsByOffer;
}

async function getReservedRanges(api) {
const logs = await getLogs({
api,
target: protocolDiamondAddress,
topics: [rangeReservedTopic],
fromBlock: protocolDeploymentBlock[api.chain],
extraKey: "reservedRanges",
eventAbi: RangeReservedEvent,
onlyArgs: true,
});

console.log("Total # of reserved ranges:", logs.length);

const reservedRangeByOffer = {};

for (const log of logs) {
const offerId = BigInt(log.offerId);
const startExchangeId = BigInt(log.startExchangeId);
const endExchangeId = BigInt(log.endExchangeId);

reservedRangeByOffer[offerId] = { startExchangeId, endExchangeId }; // there can be only one reserved range per offer
}

return reservedRangeByOffer;
}

async function getEncumberedFunds(api) {
const FundsEncumberedLogs = await getLogs({
api,
target: protocolDiamondAddress,
topics: [fundsEncumberedTopic],
fromBlock: protocolDeploymentBlock[api.chain],
eventAbi: FundsEncumberedEvent,
onlyArgs: true,
extraKey: "fundsEncumbered",
});

const encumberedByToken = {};
for (const log of FundsEncumberedLogs) {
const token = log.exchangeToken;
const amount = BigInt(log.amount);
encumberedByToken[token] = (encumberedByToken[token] || 0n) + amount;
}

const FundsReleasedLogs = await getLogs({
api,
target: protocolDiamondAddress,
topics: [fundsReleasedTopic],
fromBlock: protocolDeploymentBlock[api.chain],
eventAbi: FundsReleasedEvent,
onlyArgs: true,
extraKey: "fundsReleased",
});

for (const log of FundsReleasedLogs) {
const token = log.exchangeToken;
const amount = BigInt(log.amount);
encumberedByToken[token] -= amount;
}

return encumberedByToken;
}

module.exports = {
getVoidedOffers,
getOffers,
getCommits,
getReservedRanges,
getEncumberedFunds,
};