Skip to content

Commit 39190aa

Browse files
committed
add beacon nft verify endpoints
1 parent 8b3eaa7 commit 39190aa

File tree

9 files changed

+196
-0
lines changed

9 files changed

+196
-0
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
COINGECKO_API_KEY=
22
GOLDSKY_PROJECT_ID=
33
THIRDWEB_CLIENT_ID=
4+
TROVE_API_KEY=

.github/workflows/deploy.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ jobs:
3030
COINGECKO_API_KEY: ${{ secrets.COINGECKO_API_KEY }}
3131
GOLDSKY_PROJECT_ID: ${{ secrets.GOLDSKY_PROJECT_ID }}
3232
THIRDWEB_CLIENT_ID: ${{ secrets.THIRDWEB_CLIENT_ID }}
33+
TROVE_API_KEY: ${{ secrets.TROVE_API_KEY }}

serverless.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ provider:
1212
COINGECKO_API_KEY: ${env:COINGECKO_API_KEY}
1313
GOLDSKY_PROJECT_ID: ${env:GOLDSKY_PROJECT_ID}
1414
THIRDWEB_CLIENT_ID: ${env:THIRDWEB_CLIENT_ID}
15+
TROVE_API_KEY: ${env:TROVE_API_KEY}
1516

1617
package:
1718
individually: true
@@ -87,3 +88,16 @@ functions:
8788
- httpApi:
8889
path: /bridgeworld/harvesters/{id}/verify
8990
method: post
91+
# /beacon
92+
verifyFoundingCharacterHolders:
93+
handler: src/handlers/beacon.verifyFoundingCharacterHolders
94+
events:
95+
- httpApi:
96+
path: /beacon/founding-characters/verify
97+
method: post
98+
verifyPetHolders:
99+
handler: src/handlers/beacon.verifyPetHolders
100+
events:
101+
- httpApi:
102+
path: /beacon/pets/verify
103+
method: post

src/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ module.exports = {
22
// Addresses
33
BURN_ADDRESS: "0x0000000000000000000000000000000000000000",
44
CONTRACT_ADVANCED_QUESTING: "0x737eaf14061fe68f04ff4ca8205acf538555fcc8",
5+
CONTRACT_BEACON: "0x990eb28e378659b93a29d46ff41f08dc6316dd98",
6+
CONTRACT_BEACON_PETS_STAKING_RULES:
7+
"0x2c6c166832a4b5a501dd7a4b7acc8e13fb6968ec",
8+
CONTRACT_BEACON_QUESTING: "0xd58d40a9a1aaeebd48a90bbb8197e0772d0e9b51",
59
CONTRACT_CORRUPTION: "0x6b66d774a862539f84128f171db1940302c4671e",
610
CONTRACT_CRAFTING: "0xb9c9ed651eb173ca7fbc3a094da9ce33ec145a29",
711
CONTRACT_MAGIC: "0x539bde0d7dbd336b79148aa742883198bbf60342",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const { Contract } = require("@ethersproject/contracts");
2+
const { CONTRACT_BEACON_PETS_STAKING_RULES } = require("../constants");
3+
const { arbitrumProvider } = require("../utils/provider");
4+
5+
const beaconPetsStakingRules = new Contract(
6+
CONTRACT_BEACON_PETS_STAKING_RULES,
7+
["function beaconPetsAmountStaked(address) view returns (uint256)"],
8+
arbitrumProvider
9+
);
10+
11+
exports.getBeaconPetsAmountStaked =
12+
beaconPetsStakingRules.beaconPetsAmountStaked;

src/contracts/beaconQuesting.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { Contract } = require("@ethersproject/contracts");
2+
const { CONTRACT_BEACON_QUESTING } = require("../constants");
3+
const { arbitrumProvider } = require("../utils/provider");
4+
5+
const beaconQuesting = new Contract(
6+
CONTRACT_BEACON_QUESTING,
7+
["function getUserQuests(address) view returns (uint128,uint128,uint128)"],
8+
arbitrumProvider
9+
);
10+
11+
exports.getUserQuests = beaconQuesting.getUserQuests;

src/handlers/beacon.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { hasFoundingCharacter, hasPet } = require("../services/beacon");
2+
3+
exports.verifyFoundingCharacterHolders = async (event) => {
4+
const body = JSON.parse(event.body);
5+
const wallets = (body.wallets || body.wallet || []).map((wallet) =>
6+
wallet.toLowerCase()
7+
);
8+
console.log(
9+
"Querying Beacon Founding Character holder status for wallets:",
10+
wallets
11+
);
12+
return {
13+
success: await hasFoundingCharacter(wallets),
14+
};
15+
};
16+
17+
exports.verifyPetHolders = async (event) => {
18+
const body = JSON.parse(event.body);
19+
const wallets = (body.wallets || body.wallet || []).map((wallet) =>
20+
wallet.toLowerCase()
21+
);
22+
console.log("Querying Beacon Pet holder status for wallets:", wallets);
23+
return {
24+
success: await hasPet(wallets),
25+
};
26+
};

src/services/beacon.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const { BigNumber } = require("@ethersproject/bignumber");
2+
3+
const { CONTRACT_BEACON } = require("../constants");
4+
const {
5+
getBeaconPetsAmountStaked,
6+
} = require("../contracts/beaconPetsStakingRules");
7+
const { getUserQuests } = require("../contracts/beaconQuesting");
8+
const { fetchUserInventory } = require("../utils/inventory");
9+
10+
exports.hasFoundingCharacter = async (wallets) => {
11+
if (wallets.length === 0) {
12+
return false;
13+
}
14+
15+
const results = await Promise.all([
16+
...wallets.map((wallet) => getUserQuests(wallet)),
17+
...wallets.map((wallet) =>
18+
fetchUserInventory({
19+
userAddress: wallet,
20+
collectionAddresses: [CONTRACT_BEACON],
21+
})
22+
),
23+
]);
24+
25+
const quests = results.slice(0, wallets.length);
26+
const inventories = results.slice(wallets.length);
27+
28+
return (
29+
quests.some((quest) => !BigNumber.from(0).eq(quest[2])) ||
30+
inventories
31+
.flat()
32+
.some(({ attributes }) =>
33+
attributes.some(
34+
({ type, value }) => type === "Token Type" && value === "CHARACTER"
35+
)
36+
)
37+
);
38+
};
39+
40+
exports.hasPet = async (wallets) => {
41+
if (wallets.length === 0) {
42+
return false;
43+
}
44+
45+
const results = await Promise.all([
46+
...wallets.map((wallet) => getBeaconPetsAmountStaked(wallet)),
47+
...wallets.map((wallet) =>
48+
fetchUserInventory({
49+
userAddress: wallet,
50+
collectionAddresses: [CONTRACT_BEACON],
51+
})
52+
),
53+
]);
54+
55+
const stakedAmounts = results.slice(0, wallets.length);
56+
const inventories = results.slice(wallets.length);
57+
58+
return (
59+
stakedAmounts.some((amount) => !BigNumber.from(0).eq(amount)) ||
60+
inventories
61+
.flat()
62+
.some(({ attributes }) =>
63+
attributes.some(
64+
({ type, value }) => type === "Token Type" && value === "PET"
65+
)
66+
)
67+
);
68+
};

src/utils/inventory.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
exports.fetchUserInventory = async ({
2+
userAddress,
3+
collectionAddresses = [],
4+
tokens = [],
5+
projection = "collectionAddr,collectionUrlSlug,queryUserQuantityOwned,metadata,image",
6+
}) => {
7+
const url = new URL("https://trove-api.treasure.lol/tokens-for-user");
8+
url.searchParams.append("userAddress", userAddress);
9+
url.searchParams.append("projection", projection);
10+
if (tokens.length > 0) {
11+
url.searchParams.append(
12+
"ids",
13+
tokens
14+
.map(({ address, tokenId }) => `arb/${address}/${tokenId}`)
15+
.join(",")
16+
);
17+
} else if (collectionAddresses.length > 0) {
18+
url.searchParams.append(
19+
"slugs",
20+
collectionAddresses.map((address) => `arb/${address}`).join(",")
21+
);
22+
}
23+
24+
const response = await fetch(url, {
25+
headers: {
26+
"X-API-Key": process.env.TROVE_API_KEY,
27+
},
28+
});
29+
const results = await response.json();
30+
if (!Array.isArray(results)) {
31+
throw new Error(
32+
`Error fetching user inventory: ${results?.message ?? "Unknown error"}`
33+
);
34+
}
35+
36+
return results
37+
.map(
38+
({
39+
collectionAddr: address,
40+
tokenId,
41+
metadata: { name, attributes, imageAlt },
42+
image: { uri: image, originalUri },
43+
queryUserQuantityOwned: balance,
44+
}) => ({
45+
user: userAddress,
46+
address,
47+
tokenId: Number(tokenId),
48+
name,
49+
image,
50+
imageAlt: imageAlt ?? originalUri,
51+
attributes: attributes.map(({ trait_type: type, value }) => ({
52+
type,
53+
value,
54+
})),
55+
balance,
56+
})
57+
)
58+
.sort((a, b) => a.tokenId - b.tokenId);
59+
};

0 commit comments

Comments
 (0)