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
2 changes: 2 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import queriesChainsV1 from "./queries/chains/v1";
import queriesConfigsV1 from "./queries/configs/v1";
import queriesNonceMappingsV1 from "./queries/nonce-mappings/v1";
import queriesWithdrawalRequestsV1 from "./queries/withdrawal-requests/v1";
import queriesWithdrawalRequestsV2 from "./queries/withdrawal-requests/v2";

// Requests
import requestsUnlocksV1 from "./requests/unlocks/v1";
Expand All @@ -34,6 +35,7 @@ const endpoints = [
queriesNonceMappingsV1,
queriesConfigsV1,
queriesWithdrawalRequestsV1,
queriesWithdrawalRequestsV2,
requestsUnlocksV1,
requestsWithdrawalsV1,
requestsWithdrawalsSignaturesV1,
Expand Down
2 changes: 1 addition & 1 deletion src/api/queries/configs/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default {
_req: FastifyRequestTypeBox<typeof Schema>,
reply: FastifyReplyTypeBox<typeof Schema>
) => {
const { walletClient } = await getOnchainAllocator("ethereum");
const { walletClient } = await getOnchainAllocator();

return reply.status(200).send({
configs: {
Expand Down
73 changes: 73 additions & 0 deletions src/api/queries/withdrawal-requests/v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Type } from "@fastify/type-provider-typebox";

import {
Endpoint,
ErrorResponse,
FastifyReplyTypeBox,
FastifyRequestTypeBox,
} from "../../utils";
import {
getOnchainAllocator,
getSignatureFromContract,
} from "../../../utils/onchain-allocator";
import { logger } from "../../../common/logger";
import { Hex } from "viem";

const Schema = {
params: Type.Object({
payloadId: Type.String({
description: "The payload id of the withdrawal request",
}),
chainId: Type.String({
description: "The chain id of the depository",
}),
}),
response: {
200: Type.Object({
encodedData: Type.String({
description:
"The depository payload to be executed on destination chain",
}),
signature: Type.Optional(
Type.String({
description:
"The sign data hash to be passed to the depository on exeuction",
})
),
}),
...ErrorResponse,
},
};

export default {
method: "GET",
url: "/queries/withdrawal-requests/:payloadId/v2",
schema: Schema,
handler: async (
req: FastifyRequestTypeBox<typeof Schema>,
reply: FastifyReplyTypeBox<typeof Schema>
) => {
logger.info(
"tracking",
JSON.stringify({
msg: "Querying `withdrawal-signature` request from contract",
data: req.body,
})
);
const { contract } = await getOnchainAllocator();
const encodedData = await contract.read.payloads([
req.params.payloadId as Hex,
]);

const signature = await getSignatureFromContract(
req.params.chainId,
req.params.payloadId,
encodedData
);
// TODO: return signature
return reply.status(200).send({
encodedData,
signature,
});
},
} as Endpoint;
75 changes: 75 additions & 0 deletions src/api/requests/withdrawals-signatures/v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Type } from "@fastify/type-provider-typebox";

import {
Endpoint,
ErrorResponse,
FastifyReplyTypeBox,
FastifyRequestTypeBox,
SubmitWithdrawalRequestParamsSchema,
} from "../../utils";
import { logger } from "../../../common/logger";
import { RequestHandlerService } from "../../../services/request-handler";

const Schema = {
body: Type.Object({
chainId: Type.String({
description: "The chain id of the withdrawal",
}),
payloadId: Type.String({
description: "The payload id of the withdrawal request",
}),
payloadParams: SubmitWithdrawalRequestParamsSchema,
}),
response: {
200: Type.Object({
encodedData: Type.String({
description:
"The depository payload to be executed on destination chain",
}),
signer: Type.String({
description: "The (MPC) signer address from the allocator",
}),
signature: Type.Optional(
Type.String({
description:
"The sign data hash to be passed to the depository on exeuction",
})
),
}),
...ErrorResponse,
},
};

export default {
method: "POST",
url: "/requests/withdrawals/signatures/v2",
schema: Schema,
handler: async (
req: FastifyRequestTypeBox<typeof Schema>,
reply: FastifyReplyTypeBox<typeof Schema>
) => {
logger.info(
"tracking",
JSON.stringify({
msg: "Executing `withdrawal-signature` request",
data: req.body,
})
);

const requestHandler = new RequestHandlerService();
const result = await requestHandler.handleOnChainWithdrawalSignature(
req.body
);

logger.info(
"tracking",
JSON.stringify({
msg: "Executed `withdrawal-signature` request",
data: req.body,
result,
})
);

return reply.status(200).send(result);
},
} as Endpoint;
32 changes: 4 additions & 28 deletions src/api/requests/withdrawals/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,13 @@ import {
ErrorResponse,
FastifyReplyTypeBox,
FastifyRequestTypeBox,
SubmitWithdrawalRequestParamsSchema,
} from "../../utils";
import { getChain } from "../../../common/chains";
import { externalError } from "../../../common/error";
import { logger } from "../../../common/logger";
import { RequestHandlerService } from "../../../services/request-handler";

const SubmitWithdrawalRequestParamsSchema = Type.Object({
chainId: Type.String({
description: "The chain id of the allocator",
}),
depository: Type.String({
description: "The depository address of the allocator",
}),
currency: Type.String({
description: "The currency to withdraw",
}),
amount: Type.String({
description: "The amount to withdraw",
}),
spender: Type.String({
description: "The address of the spender",
}),
receiver: Type.String({
description: "The address of the receiver on the depository chain",
}),
data: Type.String({
description: "The data to include in the withdrawal request",
}),
nonce: Type.String({
description: "The nonce to include in the withdrawal request",
}),
});

const Schema = {
body: Type.Object({
mode: Type.Optional(
Expand All @@ -66,7 +40,9 @@ const Schema = {
description:
"Signature attesting the owner authorized this particular withdrawal request",
}),
submitWithdrawalRequestParams: Type.Optional(SubmitWithdrawalRequestParamsSchema),
submitWithdrawalRequestParams: Type.Optional(
SubmitWithdrawalRequestParamsSchema
),
additionalData: Type.Optional(
Type.Object(
{
Expand Down
152 changes: 152 additions & 0 deletions src/api/requests/withdrawals/v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Type } from "@fastify/type-provider-typebox";
import { createHash } from "crypto";
import stringify from "json-stable-stringify";
import { Address, Hex, verifyMessage } from "viem";

import {
Endpoint,
ErrorResponse,
FastifyReplyTypeBox,
FastifyRequestTypeBox,
SubmitWithdrawalRequestParamsSchema,
} from "../../utils";
import { getChain } from "../../../common/chains";
import { externalError } from "../../../common/error";
import { logger } from "../../../common/logger";
import { RequestHandlerService } from "../../../services/request-handler";

const Schema = {
body: Type.Object({
chainId: Type.String({
description: "The chain id to withdraw on",
}),
currency: Type.String({
description: "The address of the currency to withdraw",
}),
amount: Type.String({
description: "The amount to withdraw",
}),
recipient: Type.String({
description: "The address of the recipient for the withdrawal proceeds",
}),
spender: Type.String({
description:
"The address of the spender (usually the withdrawal address)",
}),
proofOfWithdrawalAddressBalance: Type.String({
description:
"The proof that withdrawal addres has funds returned by the oracle",
}),
owner: Type.String({
description: "The address of the owner (that triggered the withdrawal)",
}),
nonce: Type.String({
description:
"The nonce to be used when submitting the withdrawal request to the allocator",
}),
signature: Type.String({
description:
"Signature attesting the owner authorized this particular withdrawal request",
}),
additionalData: Type.Optional(
Type.Object(
{
"hyperliquid-vm": Type.Optional(
Type.Object({
currencyHyperliquidSymbol: Type.String({
description: "The Hyperliquid symbol for the currency",
}),
})
),
},
{
description:
"Additional data needed for generating the withdrawal request",
}
)
),
}),
response: {
200: Type.Object({
id: Type.String({ description: "The id of the withdrawal" }),
encodedData: Type.String({
description:
"The withdrawal data (encoded based on the withdrawing chain's vm type)",
}),
signer: Type.String({ description: "The signer of the withdrawal" }),
submitWithdrawalRequestParams: Type.Optional(
SubmitWithdrawalRequestParamsSchema
),
signature: Type.Optional(
Type.String({
description: "The allocator signature for the withdrawal",
})
),
}),
...ErrorResponse,
},
};

export default {
method: "POST",
url: "/requests/withdrawals/v2",
schema: Schema,
handler: async (
req: FastifyRequestTypeBox<typeof Schema>,
reply: FastifyReplyTypeBox<typeof Schema>
) => {
// TODO: use ownerChainId to allow withdrawals from any chain
const signatureVmType = await getChain(req.body.chainId).then(
(c) => c.vmType
);
if (signatureVmType !== "ethereum-vm") {
throw externalError(
"Only 'ethereum-vm' signatures are supported",
"UNSUPPORTED_SIGNATURE"
);
}

const hash = createHash("sha256")
.update(
stringify({
...req.body,
signature: undefined,
})!
)
.digest("hex");
const isSignatureValid = await verifyMessage({
address: req.body.owner as Address,
message: {
raw: `0x${hash}`,
},
signature: req.body.signature as Hex,
});
if (!isSignatureValid) {
throw externalError("Invalid signature", "INVALID_SIGNATURE");
}

logger.info(
"tracking",
JSON.stringify({
msg: "Executing `withdrawal` request",
request: req.body,
})
);

const requestHandler = new RequestHandlerService();
// Extract only the fields expected by the handler (exclude signature which is only for validation)
const { signature: _, ...requestBody } = req.body;
const result = await requestHandler.handleOnChainWithdrawal(requestBody);

logger.info(
"tracking",
JSON.stringify({
msg: "Executed `withdrawal` request",
request: req.body,
result,
})
);

return reply.status(200).send(result);
},
} as Endpoint;
Loading