Skip to content

Commit 92987b2

Browse files
authored
Merge pull request #260 from framesjs/feat/debugger-mint-tx
feat: mint tx via reservoir api
2 parents 3875369 + 306673d commit 92987b2

File tree

5 files changed

+203
-10
lines changed

5 files changed

+203
-10
lines changed

.changeset/gorgeous-crabs-tie.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@frames.js/debugger": patch
3+
---
4+
5+
feat: mint tx via reservoir api

packages/debugger/app/mint/route.tsx

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { createClient, reservoirChains } from "@reservoir0x/reservoir-sdk";
2+
import { TransactionTargetResponse, getTokenFromUrl } from "frames.js";
3+
import { NextRequest, NextResponse } from "next/server";
4+
import {
5+
createPublicClient,
6+
createWalletClient,
7+
hexToBigInt,
8+
http,
9+
parseAbi,
10+
} from "viem";
11+
import * as viemChains from "viem/chains";
12+
13+
export async function GET(request: NextRequest) {
14+
try {
15+
const searchParams = request.nextUrl.searchParams;
16+
const taker = searchParams.get("taker");
17+
const target = searchParams.get("target"); // CAIP-10 ID
18+
const referrer = searchParams.get("referrer") || undefined;
19+
20+
if (!taker || !target) {
21+
throw new Error("Missing required parameters");
22+
}
23+
24+
// Extract contract, type, and chain ID from itemId
25+
const {
26+
address: contractAddress,
27+
chainId,
28+
tokenId,
29+
} = getTokenFromUrl(target);
30+
31+
const reservoirChain = [...Object.values(reservoirChains)].find(
32+
(chain) => chain.id === chainId
33+
);
34+
35+
const viemChain = Object.values(viemChains).find(
36+
(chain) => chain.id === chainId
37+
);
38+
39+
if (!reservoirChain || !viemChain) {
40+
throw new Error("Unsupported chain");
41+
}
42+
43+
const publicClient = createPublicClient({
44+
chain: viemChain,
45+
transport: http(),
46+
});
47+
48+
const ERC1155_ERC165 = "0xd9b67a26";
49+
const ERC721_ERC165 = "0x80ac58cd";
50+
51+
async function supportsInterface(
52+
interfaceId: `0x${string}`
53+
): Promise<boolean> {
54+
return await publicClient
55+
.readContract({
56+
address: contractAddress as `0x${string}`,
57+
abi: parseAbi([
58+
"function supportsInterface(bytes4 interfaceID) external view returns (bool)",
59+
]),
60+
functionName: "supportsInterface",
61+
args: [interfaceId],
62+
})
63+
.catch((err) => {
64+
console.error(err);
65+
return false;
66+
});
67+
}
68+
69+
// Get token type
70+
const [isERC721, isERC1155] = await Promise.all([
71+
supportsInterface(ERC721_ERC165),
72+
supportsInterface(ERC1155_ERC165),
73+
]);
74+
75+
let buyTokenPartial: { token?: string; collection?: string };
76+
if (isERC721) {
77+
buyTokenPartial = { collection: contractAddress };
78+
} else if (isERC1155) {
79+
buyTokenPartial = { token: `${contractAddress}:${tokenId}` };
80+
} else {
81+
buyTokenPartial = { collection: contractAddress };
82+
}
83+
84+
// Create reservoir client with applicable chain
85+
const reservoirClient = createClient({
86+
chains: [{ ...reservoirChain, active: true }],
87+
});
88+
89+
const wallet = createWalletClient({
90+
account: taker as `0x${string}`,
91+
transport: http(),
92+
chain: viemChain,
93+
});
94+
95+
const res = await reservoirClient.actions.buyToken({
96+
items: [{ ...buyTokenPartial, quantity: 1, fillType: "mint" }],
97+
options: {
98+
referrer,
99+
},
100+
wallet,
101+
precheck: true,
102+
onProgress: () => void 0,
103+
});
104+
105+
if (res === true) {
106+
return NextResponse.json(res);
107+
}
108+
109+
const mintTx = res.steps?.find((step) => step?.id === "sale")?.items?.[0];
110+
111+
const txResponse: TransactionTargetResponse = {
112+
chainId: `eip155:${viemChain.id}`,
113+
method: "eth_sendTransaction",
114+
params: {
115+
...mintTx!.data,
116+
value: hexToBigInt(mintTx?.data.value).toString(),
117+
},
118+
};
119+
120+
return NextResponse.json({
121+
data: txResponse,
122+
explorer: viemChain.blockExplorers?.default,
123+
});
124+
} catch (err: any) {
125+
return NextResponse.json(
126+
{ message: err.response?.data?.message || err.message },
127+
{ status: err.status ?? 400 }
128+
);
129+
}
130+
}

packages/debugger/app/page.tsx

+31
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,37 @@ export default function App({
116116
signerState,
117117
extraButtonRequestPayload: { mockData: mockHubContext },
118118
onTransaction,
119+
onMint(t) {
120+
if (!confirm(`Mint ${t.target}?`)) {
121+
return;
122+
}
123+
124+
if (!account.address) {
125+
openConnectModal?.();
126+
return;
127+
}
128+
129+
const searchParams = new URLSearchParams({
130+
target: t.target,
131+
taker: account.address,
132+
});
133+
134+
fetch(`/mint?${searchParams.toString()}`)
135+
.then(async (res) => {
136+
if (!res.ok) {
137+
const json = await res.json();
138+
throw new Error(json.message);
139+
}
140+
return await res.json();
141+
})
142+
.then((json) => {
143+
onTransaction({ ...t, transactionData: json.data });
144+
})
145+
.catch((e) => {
146+
alert(e);
147+
console.error(e);
148+
});
149+
},
119150
});
120151

121152
return (

packages/debugger/package.json

+11-10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"url": "https://github.com/framesjs/frames.js/tree/main/packages/debugger"
1717
},
1818
"dependencies": {
19+
"@farcaster/core": "^0.14.7",
1920
"@frames.js/render": "^0.0.2",
2021
"@noble/ed25519": "^2.0.0",
2122
"@radix-ui/react-accordion": "^1.1.2",
@@ -25,6 +26,9 @@
2526
"@radix-ui/react-slot": "^1.0.2",
2627
"@radix-ui/react-switch": "^1.0.3",
2728
"@radix-ui/react-tabs": "^1.0.4",
29+
"@rainbow-me/rainbowkit": "^2.0.2",
30+
"@reservoir0x/reservoir-sdk": "^2.0.11",
31+
"@tanstack/react-query": "^5.22.2",
2832
"@types/node": "^18.17.0",
2933
"@types/react": "^18.2.0",
3034
"@types/react-dom": "^18.2.0",
@@ -35,25 +39,22 @@
3539
"clsx": "^2.1.0",
3640
"eslint": "^8.56.0",
3741
"eslint-config-next": "^14.1.0",
42+
"frames.js": "^0.11.0",
3843
"is-port-reachable": "^4.0.0",
3944
"lucide-react": "^0.344.0",
45+
"next": "^14.1.3",
4046
"open": "^10.0.3",
4147
"postcss": "^8",
4248
"qrcode.react": "^3.1.0",
49+
"react": "^18.2.0",
50+
"react-dom": "^18.2.0",
4351
"tailwind-merge": "^2.2.1",
4452
"tailwindcss": "^3.3.0",
4553
"tailwindcss-animate": "^1.0.7",
4654
"typescript": "^5.3.3",
47-
"yargs": "^17.7.2",
48-
"@farcaster/core": "^0.14.7",
49-
"@rainbow-me/rainbowkit": "^2.0.2",
50-
"@tanstack/react-query": "^5.22.2",
51-
"frames.js": "^0.11.0",
52-
"next": "^14.1.3",
53-
"react": "^18.2.0",
54-
"react-dom": "^18.2.0",
5555
"viem": "^2.7.12",
56-
"wagmi": "^2.5.7"
56+
"wagmi": "^2.5.7",
57+
"yargs": "^17.7.2"
5758
},
5859
"engines": {
5960
"node": ">=18.17.0"
@@ -79,4 +80,4 @@
7980
"root": true,
8081
"extends": "next"
8182
}
82-
}
83+
}

yarn.lock

+26
Original file line numberDiff line numberDiff line change
@@ -3037,6 +3037,13 @@
30373037
dependencies:
30383038
web-streams-polyfill "^3.1.1"
30393039

3040+
"@reservoir0x/reservoir-sdk@^2.0.11":
3041+
version "2.0.11"
3042+
resolved "https://registry.yarnpkg.com/@reservoir0x/reservoir-sdk/-/reservoir-sdk-2.0.11.tgz#400310406d7137119364f124630b61f1973ff57f"
3043+
integrity sha512-ha9UrNKyFNB0OSZ82NROA74gKYPo50syOcqI3DeBbSkTR0auioKykmf3kn9WWcrgbZ2km5EONtJ6uwhXFzxfiw==
3044+
dependencies:
3045+
axios "^1.6.7"
3046+
30403047
30413048
version "2.4.0"
30423049
resolved "https://registry.yarnpkg.com/@resvg/resvg-wasm/-/resvg-wasm-2.4.0.tgz#e01164b9a267c822e1ff797daa2fb91b663ea6f0"
@@ -4926,6 +4933,15 @@ axe-core@=4.7.0:
49264933
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
49274934
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
49284935

4936+
axios@^1.6.7:
4937+
version "1.6.8"
4938+
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66"
4939+
integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==
4940+
dependencies:
4941+
follow-redirects "^1.15.6"
4942+
form-data "^4.0.0"
4943+
proxy-from-env "^1.1.0"
4944+
49294945
axobject-query@^3.2.1:
49304946
version "3.2.1"
49314947
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
@@ -7488,6 +7504,11 @@ flatted@^3.2.9:
74887504
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
74897505
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
74907506

7507+
follow-redirects@^1.15.6:
7508+
version "1.15.6"
7509+
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
7510+
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
7511+
74917512
for-each@^0.3.3:
74927513
version "0.3.3"
74937514
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -11984,6 +12005,11 @@ [email protected]:
1198412005
resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.5.1.tgz#17818e33d1653fbac8c2ec31406bce8a2966f600"
1198512006
integrity sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==
1198612007

12008+
proxy-from-env@^1.1.0:
12009+
version "1.1.0"
12010+
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
12011+
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
12012+
1198712013
pseudomap@^1.0.2:
1198812014
version "1.0.2"
1198912015
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"

0 commit comments

Comments
 (0)