Skip to content
Open
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
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
ZKLogin implementation using Mysten Labs' Enoki service for user's salt management and prover service.

This template includes:
* ZKLogin using google
* Transferring SUI to wallet address
* Minting a sample NFT with and without using sponsored transaction.

- ZKLogin using google
- Transferring SUI to wallet address
- Minting a sample NFT with and without using sponsored transaction.
- Support for sponsored transactions using private API from enoki, using a backend service.

Note: This implementation of sponsored transactions bypasses the current limitation of Enoki's service by supporting them through self-owned wallet.

Node versions: v18 and above for the frontend, v20 and above for the backend.
2 changes: 2 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PORT=
ENOKI_PRIVATE_API_KEY=
19 changes: 19 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "sui-zklogin-enoki",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "nodemon src/index.js"
},
"author": "Saajan Duwal",
"license": "ISC",
"dependencies": {
"@mysten/enoki": "^0.4.4",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"nodemon": "^3.1.7"
}
}
78 changes: 78 additions & 0 deletions backend/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import "dotenv/config";
import express from "express";
import cors from "cors";

const app = express();
const port = process.env.PORT || 8000;

import { executeTx, sponsorTx } from "./services/sponsoredServices.js";

app.use(cors());
app.use(express.json());

app.post("/api/v1/sponsor", async (req, res) => {
const { network, sender, txBytes, allowedAddresses, allowedMoveCallTargets } =
req.body;

if (!network || !sender || !txBytes) {
return res.status(400).json({
error: "Missing necessary parameters.",
});
}

try {
const sponsoredTxRes = await sponsorTx({
network,
sender,
transactionKindBytes: txBytes,
allowedAddresses,
allowedMoveCallTargets,
});

return res.status(200).json({
bytes: sponsoredTxRes.bytes,
digest: sponsoredTxRes.digest,
});
} catch (error) {
console.error(error);
return res
.status(500)
.json({ error: "An error occurred while processing the transaction." });
}
});

app.post("/api/v1/execute", async (req, res) => {
const { digest, signature } = req.body;

if (!digest || !signature) {
return res.status(400).json({
error: "Missing necessary parameters.",
});
}

try {
const sponsoredTxRes = await executeTx({
digest,
signature,
});

if (!sponsoredTxRes.digest) {
return res.status(400).json({
error: "Failed to get digest for the transaction.",
});
}

return res.status(200).json({
digest: sponsoredTxRes.digest,
});
} catch (error) {
console.error(error);
return res
.status(500)
.json({ error: "An error occurred while processing the transaction." });
}
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
39 changes: 39 additions & 0 deletions backend/src/services/sponsoredServices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { EnokiClient } from "@mysten/enoki";

const ENOKI_PRIVATE_API_KEY = process.env.ENOKI_PRIVATE_API_KEY;

export const sponsorTx = async (txData) => {
try {
const enokiClient = new EnokiClient({
apiKey: ENOKI_PRIVATE_API_KEY,
});

const sponsoredTxRes = await enokiClient.createSponsoredTransaction({
network: txData.network || "mainnet",
transactionKindBytes: txData.transactionKindBytes,
sender: txData.sender || "",
allowedAddresses: txData.allowedAddresses,
allowedMoveCallTargets: txData.allowedMoveCallTargets,
});
console.log("sponsoredTxRes", sponsoredTxRes);
return sponsoredTxRes;
} catch (error) {
throw error;
}
};

export const executeTx = async (txData) => {
try {
const enokiClient = new EnokiClient({
apiKey: ENOKI_PRIVATE_API_KEY,
});
const executeTxRes = await enokiClient.executeSponsoredTransaction({
digest: txData.digest,
signature: txData.signature,
});
console.log("executeTxRes", executeTxRes);
return executeTxRes;
} catch (error) {
throw error;
}
};
Loading