-
Notifications
You must be signed in to change notification settings - Fork 91
[wip] feat: crud endpoints for Engine lite #811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
20 changes: 20 additions & 0 deletions
20
src/prisma/migrations/20241211085444_add_backend_wallet_lite_acccess_table/migration.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
-- CreateTable | ||
CREATE TABLE "backend_wallet_lite_access" ( | ||
"id" TEXT NOT NULL, | ||
"teamId" TEXT NOT NULL, | ||
"dashboardUserAddress" TEXT NOT NULL, | ||
"accountAddress" TEXT, | ||
"signerAddress" TEXT, | ||
"encryptedJson" TEXT, | ||
"salt" TEXT NOT NULL, | ||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"deletedAt" TIMESTAMP(3), | ||
|
||
CONSTRAINT "backend_wallet_lite_access_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "backend_wallet_lite_access_teamId_idx" ON "backend_wallet_lite_access"("teamId"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "backend_wallet_lite_access" ADD CONSTRAINT "backend_wallet_lite_access_accountAddress_fkey" FOREIGN KEY ("accountAddress") REFERENCES "wallet_details"("address") ON DELETE SET NULL ON UPDATE CASCADE; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,90 @@ | ||
import type { FastifyInstance } from "fastify"; | ||
import type { FastifyInstance, FastifyRequest } from "fastify"; | ||
import { stringify } from "thirdweb/utils"; | ||
import { logger } from "../../shared/utils/logger"; | ||
import { ADMIN_QUEUES_BASEPATH } from "./admin-routes"; | ||
import { OPENAPI_ROUTES } from "./open-api"; | ||
|
||
const SKIP_LOG_PATHS = new Set([ | ||
"", | ||
const IGNORE_LOG_PATHS = new Set([ | ||
"/", | ||
"/favicon.ico", | ||
"/system/health", | ||
"/static", | ||
...OPENAPI_ROUTES, | ||
// Skip these routes case of importing sensitive details. | ||
]); | ||
|
||
const SENSITIVE_LOG_PATHS = new Set([ | ||
"/backend-wallet/import", | ||
"/configuration/wallets", | ||
"/backend-wallet/lite", | ||
]); | ||
|
||
function shouldLog(request: FastifyRequest) { | ||
if (!request.routeOptions.url) { | ||
return false; | ||
} | ||
if (request.method === "OPTIONS") { | ||
return false; | ||
} | ||
if ( | ||
request.method === "POST" && | ||
SENSITIVE_LOG_PATHS.has(request.routeOptions.url) | ||
) { | ||
return false; | ||
} | ||
if (IGNORE_LOG_PATHS.has(request.routeOptions.url)) { | ||
return false; | ||
} | ||
if (request.routeOptions.url.startsWith(ADMIN_QUEUES_BASEPATH)) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
export function withRequestLogs(server: FastifyInstance) { | ||
server.addHook("onSend", async (request, reply, payload) => { | ||
if ( | ||
request.method === "OPTIONS" || | ||
!request.routeOptions.url || | ||
SKIP_LOG_PATHS.has(request.routeOptions.url) || | ||
request.routeOptions.url.startsWith(ADMIN_QUEUES_BASEPATH) | ||
) { | ||
return payload; | ||
} | ||
|
||
const { method, routeOptions, headers, params, query, body } = request; | ||
const { statusCode, elapsedTime } = reply; | ||
const isError = statusCode >= 400; | ||
if (shouldLog(request)) { | ||
const { method, routeOptions, headers, params, query, body } = request; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Random whitespace changes in this file. I confirmed its using spaces now, so I must have saved it with tabs in the past. (Grrr Biome) |
||
const { statusCode, elapsedTime } = reply; | ||
const isError = statusCode >= 400; | ||
|
||
const extractedHeaders = { | ||
"x-backend-wallet-address": headers["x-backend-wallet-address"], | ||
"x-idempotency-key": headers["x-idempotency-key"], | ||
"x-account-address": headers["x-account-address"], | ||
"x-account-factory-address": headers["x-account-factory-address"], | ||
"x-account-salt": headers["x-account-salt"], | ||
}; | ||
const extractedHeaders = { | ||
"x-backend-wallet-address": headers["x-backend-wallet-address"], | ||
"x-idempotency-key": headers["x-idempotency-key"], | ||
"x-account-address": headers["x-account-address"], | ||
"x-account-factory-address": headers["x-account-factory-address"], | ||
"x-account-salt": headers["x-account-salt"], | ||
}; | ||
|
||
const paramsStr = | ||
params && Object.keys(params).length | ||
? `params=${stringify(params)}` | ||
: undefined; | ||
const queryStr = | ||
query && Object.keys(query).length | ||
? `querystring=${stringify(query)}` | ||
: undefined; | ||
const bodyStr = | ||
body && Object.keys(body).length ? `body=${stringify(body)}` : undefined; | ||
const payloadStr = isError ? `payload=${payload}` : undefined; | ||
const paramsStr = | ||
params && Object.keys(params).length | ||
? `params=${stringify(params)}` | ||
: undefined; | ||
const queryStr = | ||
query && Object.keys(query).length | ||
? `querystring=${stringify(query)}` | ||
: undefined; | ||
const bodyStr = | ||
body && Object.keys(body).length | ||
? `body=${stringify(body)}` | ||
: undefined; | ||
const payloadStr = isError ? `payload=${payload}` : undefined; | ||
|
||
logger({ | ||
service: "server", | ||
level: isError ? "error" : "info", | ||
message: [ | ||
`[Request complete - ${statusCode}]`, | ||
`method=${method}`, | ||
`path=${routeOptions.url}`, | ||
`headers=${stringify(extractedHeaders)}`, | ||
paramsStr, | ||
queryStr, | ||
bodyStr, | ||
`duration=${elapsedTime.toFixed(1)}ms`, | ||
payloadStr, | ||
].join(" "), | ||
}); | ||
logger({ | ||
service: "server", | ||
level: isError ? "error" : "info", | ||
message: [ | ||
`[Request complete - ${statusCode}]`, | ||
`method=${method}`, | ||
`path=${routeOptions.url}`, | ||
`headers=${stringify(extractedHeaders)}`, | ||
paramsStr, | ||
queryStr, | ||
bodyStr, | ||
`duration=${elapsedTime.toFixed(1)}ms`, | ||
payloadStr, | ||
].join(" "), | ||
}); | ||
} | ||
|
||
return payload; | ||
}); | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { type Static, Type } from "@sinclair/typebox"; | ||
import type { FastifyInstance } from "fastify"; | ||
import { StatusCodes } from "http-status-codes"; | ||
import { checksumAddress } from "thirdweb/utils"; | ||
import { getBackendWalletLiteAccess } from "../../../../shared/db/wallets/get-backend-wallet-lite-access"; | ||
import { AddressSchema } from "../../../schemas/address"; | ||
import { standardResponseSchema } from "../../../schemas/shared-api-schemas"; | ||
import { createCustomError } from "../../../middleware/error"; | ||
import { | ||
DEFAULT_ACCOUNT_FACTORY_V0_7, | ||
ENTRYPOINT_ADDRESS_v0_7, | ||
} from "thirdweb/wallets/smart"; | ||
import { createSmartLocalWalletDetails } from "../../../utils/wallets/create-smart-wallet"; | ||
import { updateBackendWalletLiteAccess } from "../../../../shared/db/wallets/update-backend-wallet-lite-access"; | ||
|
||
const requestSchema = Type.Object({ | ||
teamId: Type.String({ | ||
description: "Wallets are listed for this team.", | ||
}), | ||
}); | ||
|
||
const requestBodySchema = Type.Object({ | ||
salt: Type.String(), | ||
litePassword: Type.String(), | ||
}); | ||
|
||
const responseSchema = Type.Object({ | ||
result: Type.Object({ | ||
walletAddress: Type.Union([AddressSchema, Type.Null()], { | ||
description: "The Engine Lite wallet address, if created.", | ||
}), | ||
salt: Type.String({ | ||
description: "The salt used to encrypt the Engine Lite wallet address..", | ||
}), | ||
}), | ||
}); | ||
|
||
responseSchema.example = { | ||
result: { | ||
walletAddress: "0x....", | ||
salt: "2caaddce3d66ed4bee1a6ba9a29c98eb6d375635f62941655702bdff74939023", | ||
}, | ||
}; | ||
|
||
export const createBackendWalletLiteRoute = async ( | ||
fastify: FastifyInstance, | ||
) => { | ||
fastify.withTypeProvider().route<{ | ||
Params: Static<typeof requestSchema>; | ||
Body: Static<typeof requestBodySchema>; | ||
Reply: Static<typeof responseSchema>; | ||
}>({ | ||
method: "POST", | ||
url: "/backend-wallet/lite/:teamId", | ||
schema: { | ||
summary: "Create backend wallet (Lite)", | ||
description: "Create a backend wallet used for Engine Lite.", | ||
tags: ["Backend Wallet"], | ||
operationId: "createBackendWalletsLite", | ||
params: requestSchema, | ||
body: requestBodySchema, | ||
response: { | ||
...standardResponseSchema, | ||
[StatusCodes.OK]: responseSchema, | ||
}, | ||
hide: true, | ||
}, | ||
handler: async (req, reply) => { | ||
const dashboardUserAddress = checksumAddress(req.user.address); | ||
if (!dashboardUserAddress) { | ||
throw createCustomError( | ||
"This endpoint must be called from the thirdweb dashboard.", | ||
StatusCodes.FORBIDDEN, | ||
"DASHBOARD_AUTH_REQUIRED", | ||
); | ||
} | ||
|
||
const { teamId } = req.params; | ||
const { salt, litePassword } = req.body; | ||
|
||
const liteAccess = await getBackendWalletLiteAccess({ teamId }); | ||
if ( | ||
!liteAccess || | ||
liteAccess.teamId !== teamId || | ||
liteAccess.dashboardUserAddress !== dashboardUserAddress || | ||
liteAccess.salt !== salt | ||
) { | ||
throw createCustomError( | ||
"The salt does not match the authenticated user. Try requesting a backend wallet again.", | ||
StatusCodes.BAD_REQUEST, | ||
"INVALID_LITE_WALLET_SALT", | ||
); | ||
} | ||
|
||
// Generate a signer wallet and store the smart:local wallet, encrypted with `litePassword`. | ||
const walletDetails = await createSmartLocalWalletDetails({ | ||
label: `${teamId} (${new Date()})`, | ||
accountFactoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_7, | ||
entrypointAddress: ENTRYPOINT_ADDRESS_v0_7, | ||
encryptionPassword: litePassword, | ||
}); | ||
if (!walletDetails.accountSignerAddress || !walletDetails.encryptedJson) { | ||
throw new Error( | ||
"Created smart:local wallet is missing required fields.", | ||
); | ||
} | ||
|
||
await updateBackendWalletLiteAccess({ | ||
id: liteAccess.id, | ||
accountAddress: walletDetails.address, | ||
signerAddress: walletDetails.accountSignerAddress, | ||
encryptedJson: walletDetails.encryptedJson, | ||
}); | ||
|
||
reply.status(StatusCodes.OK).send({ | ||
result: { | ||
walletAddress: walletDetails.address, | ||
salt, | ||
}, | ||
}); | ||
}, | ||
}); | ||
}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small changes here to add the
POST /backend-wallet/lite
path to not log the litePassword