Skip to content

Commit

Permalink
real inscriptions incoming
Browse files Browse the repository at this point in the history
  • Loading branch information
0xFlicker committed Feb 3, 2024
1 parent a528e27 commit 776487e
Show file tree
Hide file tree
Showing 22 changed files with 800 additions and 114 deletions.
130 changes: 130 additions & 0 deletions apps/functions/src/lambdas/frame-render-qrcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { S3 } from "@aws-sdk/client-s3";
import { Canvas, loadImage } from "canvas";
import { APIGatewayProxyHandler, APIGatewayProxyHandlerV2 } from "aws-lambda";
import QRCode from "qrcode";
import { satsToBitcoin } from "@0xflick/inscriptions";

export async function createQR(content: string) {
const url = await QRCode.toDataURL(content, {
errorCorrectionLevel: "M",
color: {
dark: "#000000",
light: "#FFFFFF",
},
margin: 4,
type: "image/png",
});

return url;
}
const s3 = new S3({
region: "us-east-1",
});

if (!process.env.SEED_BUCKET) {
throw new Error("SEED_BUCKET not set");
}
if (!process.env.IMAGE_HOST) {
throw new Error("IMAGE_HOST not set");
}

const seedImageBucket = process.env.SEED_BUCKET;
const imageHost = process.env.IMAGE_HOST;

async function s3Exists({
key,
bucket,
}: {
key: string;
bucket: string;
}): Promise<boolean> {
const params = {
Bucket: bucket,
Key: key,
};
try {
await s3.headObject(params);
return true;
} catch (err) {
return false;
}
}

async function s3WriteObject(key: string, imageData: Buffer): Promise<void> {
console.log(`Writing to s3://${seedImageBucket}/${key}`);

await s3.putObject({
Bucket: seedImageBucket,
Key: key,
Body: imageData,
ContentDisposition: "inline",
ContentType: "image/png",
Expires: new Date(Date.now() + 1000 * 60 * 60 * 6),
});
}

export const handler: APIGatewayProxyHandlerV2 = async (event) => {
try {
const { pathParameters } = event;

const address = pathParameters.address;
const amount = pathParameters.amount;
const qrValue = `bitcoin:${address}?amount=${amount}`;
console.log(`qrValue: ${qrValue}`);

const s3Key = `qr/${address}/${amount}.png`;
const exists = await s3Exists({ key: s3Key, bucket: seedImageBucket });

if (!exists) {
console.log(`Qr image not found in S3: ${s3Key}`);
const qrSrc = await createQR(qrValue);
const qrImg = await loadImage(qrSrc);
// Render canvas
console.log("Creating canvas");
const canvas = new Canvas(800, 400);
const ctx = canvas.getContext("2d");

ctx.fillStyle = "black";
ctx.fillRect(0, 0, 800, 400);
ctx.drawImage(qrImg, 50, 50, 300, 300);
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.font = "30px Arial";
ctx.fillText("Payment request", 600, 100);
ctx.font = "20px Arial";
ctx.fillText(`${satsToBitcoin(BigInt(amount))} BTC`, 600, 150);
ctx.fillText("to", 600, 200);
ctx.font = "10px Arial";
ctx.fillText(address, 600, 250);

// Save canvas to S3
console.log("Fetching image from canvas");
const imageData = canvas.toBuffer("image/png", { compressionLevel: 8 });
console.log("Saving canvas to S3");
await s3WriteObject(s3Key, imageData);
console.log("Done");
return {
statusCode: 302,
headers: {
["Location"]: `https://${imageHost}/${s3Key}`,
},
body: "",
};
}
console.log(`Seed image found in S3: ${s3Key}`);
console.log("Returning image");
return {
statusCode: 302,
headers: {
["Location"]: `https://${imageHost}/${s3Key}`,
},
body: "",
};
} catch (err) {
console.error(err);
return {
statusCode: 500,
body: "Oops, something went wrong",
};
}
};
165 changes: 165 additions & 0 deletions apps/functions/src/lambdas/frame-render-teaser-axolotl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { GetObjectCommand, S3 } from "@aws-sdk/client-s3";
import { Canvas, Image, loadImage } from "canvas";
import { operations } from "@0xflick/ordinals-axolotl-valley-render";
import { ILayer } from "@0xflick/assets";
import { utils } from "ethers";
import { APIGatewayProxyHandler, APIGatewayProxyHandlerV2 } from "aws-lambda";
import { Readable } from "stream";

const s3 = new S3({
region: "us-east-1",
});

if (!process.env.ASSET_BUCKET) {
throw new Error("ASSET_BUCKET not set");
}
if (!process.env.SEED_BUCKET) {
throw new Error("SEED_BUCKET not set");
}
if (!process.env.IMAGE_HOST) {
throw new Error("IMAGE_HOST not set");
}

const generativeAssetsBucket = process.env.ASSET_BUCKET;
const seedImageBucket = process.env.SEED_BUCKET;
const imageHost = process.env.IMAGE_HOST;

async function renderCanvas(canvas: Canvas, layers: ILayer[]) {
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);

for (const layer of [...layers].sort((a, b) => a.zIndex - b.zIndex)) {
await layer.draw(ctx as any);
}
}

async function s3Exists({
key,
bucket,
}: {
key: string;
bucket: string;
}): Promise<boolean> {
const params = {
Bucket: bucket,
Key: key,
};
try {
await s3.headObject(params);
return true;
} catch (err) {
return false;
}
}

async function s3WriteObject(key: string, imageData: Buffer): Promise<void> {
console.log(`Writing to s3://${seedImageBucket}/${key}`);

await s3.putObject({
Bucket: seedImageBucket,
Key: key,
Body: imageData,
ContentDisposition: "inline",
ContentType: "image/png",
Expires: new Date(Date.now() + 1000 * 60 * 60 * 6),
});
}

export const handler: APIGatewayProxyHandlerV2 = async (event) => {
console.log("Received image request");
try {
const { pathParameters } = event;

const seedStr = pathParameters.seed;
console.log(`Seed: ${seedStr}`);

const s3Key = `axolotl/${seedStr}.png`;
const exists = await s3Exists({ key: s3Key, bucket: seedImageBucket });

if (!exists) {
console.log(`Seed image not found in S3: ${s3Key}`);
// From seed, generate layers
const { layers } = await operations(
utils.arrayify(seedStr),
async (imagePath) => {
const getObjectCommand = new GetObjectCommand({
Bucket: generativeAssetsBucket,
Key: imagePath.replace(".webp", ".PNG"),
});

try {
const response = await s3.send(getObjectCommand);
const stream = response.Body as Readable;
return new Promise<Image>((resolve, reject) => {
const responseDataChunks: Buffer[] = [];

// Handle an error while streaming the response body
stream.once("error", (err) => reject(err));

// Attach a 'data' listener to add the chunks of data to our array
// Each chunk is a Buffer instance
stream.on("data", (chunk) => responseDataChunks.push(chunk));

// Once the stream has no more data, join the chunks into a string and return the string
stream.once("end", () => {
resolve(loadImage(Buffer.concat(responseDataChunks)));
});
});
} catch (err) {
console.error(`Unable to fetch image ${imagePath}`, err);
throw err;
}
},
);

// Render canvas
console.log("Creating canvas");
const canvas = new Canvas(569, 569);
console.log("Rendering canvas");
await renderCanvas(canvas, layers);
const ogMetaCanvas = new Canvas(800, 400);
const ogMetaCtx = ogMetaCanvas.getContext("2d");
ogMetaCtx.fillStyle = "white";
ogMetaCtx.fillRect(0, 0, 800, 400);
ogMetaCtx.drawImage(canvas, 50, 50, 300, 300);
ogMetaCtx.fillStyle = "black";
ogMetaCtx.textAlign = "center";
ogMetaCtx.font = "50px Arial";
ogMetaCtx.fillText("Axolotl Valley", 580, 120);
ogMetaCtx.font = "40px Arial";
ogMetaCtx.fillText("Bitcoin Ordinal Mint", 580, 220);
ogMetaCtx.fillText("Reveals in 420 blocks", 580, 320);

// Save canvas to S3
console.log("Fetching image from canvas");
const imageData = ogMetaCanvas.toBuffer("image/png", {
compressionLevel: 8,
});
console.log("Saving canvas to S3");
await s3WriteObject(s3Key, imageData);
console.log("Done");
return {
statusCode: 302,
headers: {
["Location"]: `https://${imageHost}/${s3Key}`,
},
body: "",
};
}
console.log(`Seed image found in S3: ${s3Key}`);
console.log("Returning image");
return {
statusCode: 302,
headers: {
["Location"]: `https://${imageHost}/${s3Key}`,
},
body: "",
};
} catch (err) {
console.error(err);
return {
statusCode: 500,
body: "Oops, something went wrong",
};
}
};
3 changes: 2 additions & 1 deletion apps/workers/src/watchFundingEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export async function start(network: "mainnet" | "testnet" = "testnet") {
const fundingDao = createDynamoDbFundingDao();
const allCollections = await fundingDao.getAllCollections();
for (const collection of allCollections) {
await watchForFundings({
watchForFundings({
collectionId: collection.id,
fundingDao,
network: "mainnet",
mempoolBitcoinClient: createMempoolBitcoinClient({ network }),
pollInterval: 20000,
});
Expand Down
1 change: 1 addition & 0 deletions apps/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@apollo/client": "^3.7.17",
"@cmdcode/crypto-utils": "^1.9.8",
"@cmdcode/tapscript": "^1.2.10",
"@coinbase/onchainkit": "^0.4.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/base": "^5.0.0-beta.29",
Expand Down
Binary file added apps/www/public/images/axolotl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions apps/www/src/app/start/[collectionId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,44 @@ export const metadata = {
export default function RootLayout({
children,
}: {
params: { collectionId: string };
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta property="og:site_name" content="bitflick" />
<meta property="og:title" content="Axolotl Valley" />
<meta property="og:description" content="mint an axolotl on bitcoin" />
<meta
property="og:image"
content="https://www.bitflick.xyz/images/axolotl.png"
/>
<meta property="twitter:title" content="Axolotl Valley" />
<meta
property="twitter:description"
content="mint an axolotl on bitcoin"
/>
<meta content="verification" name="LR1011" />
<meta
property="twitter:image"
content="https://www.bitflick.xyz/images/axolotl.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@0xflick" />

{/* <meta property="fc:frame" content="vNext" />
<meta
property="fc:frame:image"
content={`https://frame.bitflick.xyz/frame-og/axolotl/${seedStr}`}
/>
<meta
property="fc:frame:post_url"
content={`https://www.bitflick.xyz/api/frame/axoltl/${collectionId}`}
/>
<meta property="fc:frame:button:1" content="claim" /> */}
</head>
<body className={inter.className}>{children}</body>
</html>
);
Expand Down
9 changes: 8 additions & 1 deletion apps/www/src/features/inscription/components/Pay/Pay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ export const Pay: FC<{
.then(() => {
setSuccessMessage("Payment sent");
setTimeout(() => {
router.push(`/${network.toLowerCase()}/status/${fundingId}`);
router.push(
`${
network === BitcoinNetworkType.Testnet ? "/testnet" : ""
}/status/${fundingId}`
);
}, 2000);
})
.catch((e) => {
Expand All @@ -85,6 +89,9 @@ export const Pay: FC<{
data?.inscriptionFunding?.fundingAddress,
data?.inscriptionFunding?.fundingAmountSats,
sendBtc,
network,
router,
fundingId,
]);

const sendXverse = useCallback(() => {
Expand Down
1 change: 1 addition & 0 deletions deploy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"license": "MIT",
"type": "module",
"scripts": {
"aws_docker_login": "aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws",
"deploy:local": "AWS_REGION=us-east-1 cdklocal deploy --outputs-file outputs.json ordinals && node ./scripts/generateTableNamesEnv.mjs",
"deploy": "cdk deploy"
},
Expand Down
Loading

0 comments on commit 776487e

Please sign in to comment.