@ecadlabs/tezosx-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +335 -0
- package/dist/adapters/index.d.ts +37 -0
- package/dist/adapters/index.js +57 -0
- package/dist/adapters/node.d.ts +18 -0
- package/dist/adapters/node.js +35 -0
- package/dist/adapters/types.d.ts +52 -0
- package/dist/adapters/types.js +25 -0
- package/dist/adapters/worker.d.ts +35 -0
- package/dist/adapters/worker.js +50 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +144 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.js +80 -0
- package/dist/tools/create_x402_payment.d.ts +27 -0
- package/dist/tools/create_x402_payment.js +55 -0
- package/dist/tools/fetch_with_x402.d.ts +28 -0
- package/dist/tools/fetch_with_x402.js +143 -0
- package/dist/tools/get_address.d.ts +20 -0
- package/dist/tools/get_address.js +24 -0
- package/dist/tools/get_addresses.d.ts +22 -0
- package/dist/tools/get_addresses.js +32 -0
- package/dist/tools/get_balance.d.ts +22 -0
- package/dist/tools/get_balance.js +27 -0
- package/dist/tools/get_dashboard.d.ts +21 -0
- package/dist/tools/get_dashboard.js +29 -0
- package/dist/tools/get_limits.d.ts +22 -0
- package/dist/tools/get_limits.js +61 -0
- package/dist/tools/get_operation_history.d.ts +21 -0
- package/dist/tools/get_operation_history.js +58 -0
- package/dist/tools/index.d.ts +113 -0
- package/dist/tools/index.js +59 -0
- package/dist/tools/parse_x402_requirements.d.ts +23 -0
- package/dist/tools/parse_x402_requirements.js +66 -0
- package/dist/tools/reveal_account.d.ts +34 -0
- package/dist/tools/reveal_account.js +51 -0
- package/dist/tools/send_xtz.d.ts +32 -0
- package/dist/tools/send_xtz.js +86 -0
- package/dist/tools/x402/sign.d.ts +12 -0
- package/dist/tools/x402/sign.js +76 -0
- package/dist/tools/x402/types.d.ts +40 -0
- package/dist/tools/x402/types.js +16 -0
- package/dist/webserver.d.ts +1 -0
- package/dist/webserver.js +10 -0
- package/dist/worker.bundle.js +134265 -0
- package/dist/worker.d.ts +13 -0
- package/dist/worker.js +132 -0
- package/frontend/dist/assets/index-RtTL1nIl.js +257 -0
- package/frontend/dist/assets/index-mSsI3AqQ.css +1 -0
- package/frontend/dist/index.html +16 -0
- package/package.json +70 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createCreateX402PaymentTool } from "./create_x402_payment.js";
|
|
2
|
+
import { createFetchWithX402Tool } from "./fetch_with_x402.js";
|
|
3
|
+
import { createGetAddressesTool } from "./get_addresses.js";
|
|
4
|
+
import { createGetBalanceTool } from "./get_balance.js";
|
|
5
|
+
import { createGetLimitsTool } from "./get_limits.js";
|
|
6
|
+
import { createGetOperationHistoryTool } from "./get_operation_history.js";
|
|
7
|
+
import { createParseX402RequirementsTool } from "./parse_x402_requirements.js";
|
|
8
|
+
import { createRevealAccountTool } from "./reveal_account.js";
|
|
9
|
+
import { createSendXtzTool } from "./send_xtz.js";
|
|
10
|
+
import { createGetDashboardTool } from "./get_dashboard.js";
|
|
11
|
+
const getNotConfiguredMessage = () => `Wallet not configured. Please set the following environment variables and restart the MCP server:
|
|
12
|
+
|
|
13
|
+
1. SPENDING_PRIVATE_KEY - Your spending key (starts with edsk, spsk, or p2sk)
|
|
14
|
+
2. SPENDING_CONTRACT - Your spending contract address (starts with KT1)
|
|
15
|
+
|
|
16
|
+
To get started:
|
|
17
|
+
1. Open the dashboard at http://localhost:${process.env.WEB_PORT || '13205'}
|
|
18
|
+
2. Connect your wallet and deploy a spending contract
|
|
19
|
+
3. Add the environment variables to your MCP client configuration
|
|
20
|
+
4. Restart the MCP server`;
|
|
21
|
+
const notConfiguredResponse = () => ({
|
|
22
|
+
content: [{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: getNotConfiguredMessage()
|
|
25
|
+
}]
|
|
26
|
+
});
|
|
27
|
+
// Wraps a tool's handler to check config before execution
|
|
28
|
+
const withConfigCheck = (tool, walletConfig) => ({
|
|
29
|
+
...tool,
|
|
30
|
+
handler: async (...args) => {
|
|
31
|
+
if (!walletConfig) {
|
|
32
|
+
return notConfiguredResponse();
|
|
33
|
+
}
|
|
34
|
+
return tool.handler(...args);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
export const createTools = (walletConfig, tzktApi, http) => {
|
|
38
|
+
// Create a mock Tezos toolkit for tool creation when config is missing
|
|
39
|
+
// The actual handlers will be blocked by withConfigCheck
|
|
40
|
+
const Tezos = walletConfig?.Tezos ?? {};
|
|
41
|
+
const spendingContract = walletConfig?.spendingContract ?? '';
|
|
42
|
+
const spendingAddress = walletConfig?.spendingAddress ?? '';
|
|
43
|
+
const tools = [
|
|
44
|
+
createCreateX402PaymentTool(Tezos),
|
|
45
|
+
createFetchWithX402Tool(Tezos),
|
|
46
|
+
createGetAddressesTool(Tezos, spendingContract),
|
|
47
|
+
createGetBalanceTool(Tezos, spendingContract, spendingAddress),
|
|
48
|
+
createGetLimitsTool(Tezos, spendingContract),
|
|
49
|
+
createGetOperationHistoryTool(spendingContract, tzktApi),
|
|
50
|
+
createParseX402RequirementsTool(),
|
|
51
|
+
createRevealAccountTool(Tezos),
|
|
52
|
+
createSendXtzTool(Tezos, spendingContract, spendingAddress),
|
|
53
|
+
];
|
|
54
|
+
if (!http) {
|
|
55
|
+
tools.push(createGetDashboardTool(spendingContract));
|
|
56
|
+
}
|
|
57
|
+
// Wrap all tools with config check
|
|
58
|
+
return tools.map(tool => withConfigCheck(tool, walletConfig));
|
|
59
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export declare const createParseX402RequirementsTool: () => {
|
|
3
|
+
name: string;
|
|
4
|
+
config: {
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: z.ZodObject<{
|
|
8
|
+
responseBody: z.ZodString;
|
|
9
|
+
}, z.z.core.$strip>;
|
|
10
|
+
annotations: {
|
|
11
|
+
readOnlyHint: boolean;
|
|
12
|
+
destructiveHint: boolean;
|
|
13
|
+
idempotentHint: boolean;
|
|
14
|
+
openWorldHint: boolean;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
handler: (params: any) => Promise<{
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { X402ResponseSchema } from "./x402/types.js";
|
|
3
|
+
export const createParseX402RequirementsTool = () => ({
|
|
4
|
+
name: "tezos_parse_x402_requirements",
|
|
5
|
+
config: {
|
|
6
|
+
title: "Parse x402 Payment Requirements",
|
|
7
|
+
description: "Parses a 402 response body containing x402 payment requirements for Tezos payments. Returns structured payment information including scheme, network, asset, amount, and recipient.",
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
responseBody: z.string().describe("The JSON response body from a 402 Payment Required response")
|
|
10
|
+
}),
|
|
11
|
+
annotations: {
|
|
12
|
+
readOnlyHint: true,
|
|
13
|
+
destructiveHint: false,
|
|
14
|
+
idempotentHint: true,
|
|
15
|
+
openWorldHint: false,
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
handler: async (params) => {
|
|
19
|
+
const { responseBody } = params;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(responseBody);
|
|
22
|
+
const validated = X402ResponseSchema.parse(parsed);
|
|
23
|
+
const tezosRequirements = validated.paymentRequirements.filter(req => req.scheme === 'exact-tezos');
|
|
24
|
+
if (tezosRequirements.length === 0) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: "No Tezos payment requirements found in the response"
|
|
29
|
+
}]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const formattedRequirements = tezosRequirements.map(req => {
|
|
33
|
+
const decimals = req.extra?.decimals ?? 6;
|
|
34
|
+
const amountInUnits = Number(req.amount) / Math.pow(10, decimals);
|
|
35
|
+
const assetName = req.extra?.name ?? req.asset;
|
|
36
|
+
return {
|
|
37
|
+
network: req.network,
|
|
38
|
+
asset: req.asset,
|
|
39
|
+
assetName,
|
|
40
|
+
amount: req.amount,
|
|
41
|
+
amountFormatted: `${amountInUnits} ${assetName}`,
|
|
42
|
+
recipient: req.recipient,
|
|
43
|
+
decimals
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
content: [{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify({
|
|
50
|
+
x402Version: validated.x402Version,
|
|
51
|
+
tezosPaymentRequirements: formattedRequirements
|
|
52
|
+
}, null, 2)
|
|
53
|
+
}]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error instanceof z.ZodError) {
|
|
58
|
+
throw new Error(`Invalid x402 response format: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof SyntaxError) {
|
|
61
|
+
throw new Error(`Invalid JSON: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* Check if an account is revealed on the Tezos network.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isAccountRevealed(Tezos: TezosToolkit, address: string): Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* Reveal an account if not already revealed.
|
|
9
|
+
* Returns true if reveal was needed and performed, false if already revealed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureRevealed(Tezos: TezosToolkit): Promise<{
|
|
12
|
+
wasRevealed: boolean;
|
|
13
|
+
opHash?: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const createRevealAccountTool: (Tezos: TezosToolkit) => {
|
|
16
|
+
name: string;
|
|
17
|
+
config: {
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
inputSchema: z.ZodObject<{}, z.z.core.$strip>;
|
|
21
|
+
annotations: {
|
|
22
|
+
readOnlyHint: boolean;
|
|
23
|
+
destructiveHint: boolean;
|
|
24
|
+
idempotentHint: boolean;
|
|
25
|
+
openWorldHint: boolean;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
handler: () => Promise<{
|
|
29
|
+
content: {
|
|
30
|
+
type: "text";
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Check if an account is revealed on the Tezos network.
|
|
4
|
+
*/
|
|
5
|
+
export async function isAccountRevealed(Tezos, address) {
|
|
6
|
+
const managerKey = await Tezos.rpc.getManagerKey(address);
|
|
7
|
+
return managerKey !== null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Reveal an account if not already revealed.
|
|
11
|
+
* Returns true if reveal was needed and performed, false if already revealed.
|
|
12
|
+
*/
|
|
13
|
+
export async function ensureRevealed(Tezos) {
|
|
14
|
+
const address = await Tezos.signer.publicKeyHash();
|
|
15
|
+
const revealed = await isAccountRevealed(Tezos, address);
|
|
16
|
+
if (revealed) {
|
|
17
|
+
return { wasRevealed: false };
|
|
18
|
+
}
|
|
19
|
+
const op = await Tezos.contract.reveal({});
|
|
20
|
+
await op.confirmation(1);
|
|
21
|
+
return { wasRevealed: true, opHash: op.hash };
|
|
22
|
+
}
|
|
23
|
+
export const createRevealAccountTool = (Tezos) => ({
|
|
24
|
+
name: "tezos_reveal_account",
|
|
25
|
+
config: {
|
|
26
|
+
title: "Reveal Account",
|
|
27
|
+
description: "Reveals the spender account's public key on the Tezos network. This is required before the account can perform any operations. The tool checks if the account is already revealed and only performs the reveal operation if needed.",
|
|
28
|
+
inputSchema: z.object({}),
|
|
29
|
+
annotations: {
|
|
30
|
+
readOnlyHint: false,
|
|
31
|
+
destructiveHint: false,
|
|
32
|
+
idempotentHint: true,
|
|
33
|
+
openWorldHint: false,
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
handler: async () => {
|
|
37
|
+
const address = await Tezos.signer.publicKeyHash();
|
|
38
|
+
const result = await ensureRevealed(Tezos);
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: JSON.stringify({
|
|
43
|
+
success: true,
|
|
44
|
+
message: result.wasRevealed ? `Account ${address} has been revealed` : `Account ${address} was already revealed`,
|
|
45
|
+
operationHash: result.opHash,
|
|
46
|
+
alreadyRevealed: !result.wasRevealed,
|
|
47
|
+
}, null, 2)
|
|
48
|
+
}]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* MCP tool for sending XTZ via a spending contract.
|
|
5
|
+
*
|
|
6
|
+
* @param Tezos - Configured TezosToolkit instance (with signer set to spender key)
|
|
7
|
+
* @param spendingContract - Address of the spending-limited wallet contract
|
|
8
|
+
* @param spendingAddress - Address of the spender account (for fee payments)
|
|
9
|
+
*/
|
|
10
|
+
export declare const createSendXtzTool: (Tezos: TezosToolkit, spendingContract: string, spendingAddress: string) => {
|
|
11
|
+
name: string;
|
|
12
|
+
config: {
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
inputSchema: z.ZodObject<{
|
|
16
|
+
toAddress: z.ZodString;
|
|
17
|
+
amount: z.ZodNumber;
|
|
18
|
+
}, z.z.core.$strip>;
|
|
19
|
+
annotations: {
|
|
20
|
+
readOnlyHint: boolean;
|
|
21
|
+
destructiveHint: boolean;
|
|
22
|
+
idempotentHint: boolean;
|
|
23
|
+
openWorldHint: boolean;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
handler: (params: any) => Promise<{
|
|
27
|
+
content: {
|
|
28
|
+
type: "text";
|
|
29
|
+
text: string;
|
|
30
|
+
}[];
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
// Constants
|
|
3
|
+
const MUTEZ_PER_TEZ = 1_000_000;
|
|
4
|
+
const CONFIRMATIONS_TO_WAIT = 3;
|
|
5
|
+
const TZKT_BASE_URL = "https://shadownet.tzkt.io";
|
|
6
|
+
// Types
|
|
7
|
+
const inputSchema = z.object({
|
|
8
|
+
toAddress: z.string().describe("The address to send Tez to."),
|
|
9
|
+
amount: z.number().describe("The amount of Tez to send to the address."),
|
|
10
|
+
});
|
|
11
|
+
// Helper Functions
|
|
12
|
+
/** Convert XTZ to mutez */
|
|
13
|
+
const xtzToMutez = (xtz) => xtz * MUTEZ_PER_TEZ;
|
|
14
|
+
/** Format mutez for display */
|
|
15
|
+
const formatMutez = (mutez) => `${mutez} mutez`;
|
|
16
|
+
/**
|
|
17
|
+
* MCP tool for sending XTZ via a spending contract.
|
|
18
|
+
*
|
|
19
|
+
* @param Tezos - Configured TezosToolkit instance (with signer set to spender key)
|
|
20
|
+
* @param spendingContract - Address of the spending-limited wallet contract
|
|
21
|
+
* @param spendingAddress - Address of the spender account (for fee payments)
|
|
22
|
+
*/
|
|
23
|
+
export const createSendXtzTool = (Tezos, spendingContract, spendingAddress) => ({
|
|
24
|
+
name: "tezos_send_xtz",
|
|
25
|
+
config: {
|
|
26
|
+
title: "Send Tez",
|
|
27
|
+
description: "Sends a set amount of Tez to another address via the spending contract.",
|
|
28
|
+
inputSchema,
|
|
29
|
+
annotations: {
|
|
30
|
+
readOnlyHint: false,
|
|
31
|
+
destructiveHint: true,
|
|
32
|
+
idempotentHint: false,
|
|
33
|
+
openWorldHint: true,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
handler: async (params) => {
|
|
37
|
+
params = params;
|
|
38
|
+
// Validate spender has funds for fees
|
|
39
|
+
const spenderBalance = await Tezos.tz.getBalance(spendingAddress);
|
|
40
|
+
if (spenderBalance.isZero()) {
|
|
41
|
+
throw new Error("Spending account balance is 0. " +
|
|
42
|
+
"Please fund the spending address to cover transaction fees.");
|
|
43
|
+
}
|
|
44
|
+
// Validate contract has funds for transfer
|
|
45
|
+
const contractBalance = await Tezos.tz.getBalance(spendingContract);
|
|
46
|
+
const amountMutez = xtzToMutez(params.amount);
|
|
47
|
+
if (contractBalance.toNumber() < amountMutez) {
|
|
48
|
+
throw new Error(`Insufficient contract balance. ` +
|
|
49
|
+
`Requested: ${formatMutez(amountMutez)}, ` +
|
|
50
|
+
`Available: ${formatMutez(contractBalance.toNumber())}`);
|
|
51
|
+
}
|
|
52
|
+
// Prepare contract call
|
|
53
|
+
const contract = await Tezos.contract.at(spendingContract);
|
|
54
|
+
const contractCall = contract.methodsObject.spend({
|
|
55
|
+
recipient: params.toAddress,
|
|
56
|
+
amount: amountMutez,
|
|
57
|
+
});
|
|
58
|
+
// Estimate fees
|
|
59
|
+
let estimate;
|
|
60
|
+
try {
|
|
61
|
+
estimate = await Tezos.estimate.contractCall(contractCall);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
65
|
+
if (message.includes("balance_too_low")) {
|
|
66
|
+
throw new Error(`Spender balance (${formatMutez(spenderBalance.toNumber())}) ` +
|
|
67
|
+
`is too low to cover fees. Please fund the spending address.`);
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
// Run another fee check because the first check is only estimating the fees, but still needs tez in the account to estimate.
|
|
72
|
+
// This check is checking against the actual estimated fee value.
|
|
73
|
+
if (spenderBalance.toNumber() < estimate.totalCost) {
|
|
74
|
+
throw new Error(`Spender balance too low for fees. ` +
|
|
75
|
+
`Required: ${formatMutez(estimate.totalCost)}, ` +
|
|
76
|
+
`Available: ${formatMutez(spenderBalance.toNumber())}`);
|
|
77
|
+
}
|
|
78
|
+
// Execute transaction
|
|
79
|
+
const operation = await contractCall.send();
|
|
80
|
+
await operation.confirmation(CONFIRMATIONS_TO_WAIT);
|
|
81
|
+
const tzktUrl = `${TZKT_BASE_URL}/${operation.hash}`;
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: tzktUrl }],
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TezosToolkit } from "@taquito/taquito";
|
|
2
|
+
import { X402PaymentPayload } from "./types.js";
|
|
3
|
+
export interface SignPaymentParams {
|
|
4
|
+
network: string;
|
|
5
|
+
amount: number;
|
|
6
|
+
recipient: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SignedPayment {
|
|
9
|
+
payload: X402PaymentPayload;
|
|
10
|
+
base64: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function signX402Payment(Tezos: TezosToolkit, params: SignPaymentParams): Promise<SignedPayment>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { OpKind } from "@taquito/taquito";
|
|
2
|
+
import { LocalForger } from "@taquito/local-forging";
|
|
3
|
+
export async function signX402Payment(Tezos, params) {
|
|
4
|
+
const { network, amount, recipient } = params;
|
|
5
|
+
// Get source address and public key from the signer
|
|
6
|
+
let source;
|
|
7
|
+
let publicKey;
|
|
8
|
+
try {
|
|
9
|
+
source = await Tezos.signer.publicKeyHash();
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new Error(`Failed to get source address from signer. Is SPENDING_PRIVATE_KEY set correctly? Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
publicKey = await Tezos.signer.publicKey();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error(`Failed to get public key from signer. Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
19
|
+
}
|
|
20
|
+
// Validate we got proper values
|
|
21
|
+
if (!source.startsWith('tz1') && !source.startsWith('tz2') && !source.startsWith('tz3')) {
|
|
22
|
+
throw new Error(`Invalid source address format: ${source}`);
|
|
23
|
+
}
|
|
24
|
+
if (!publicKey.startsWith('edpk') && !publicKey.startsWith('sppk') && !publicKey.startsWith('p2pk')) {
|
|
25
|
+
throw new Error(`Invalid public key format: ${publicKey}`);
|
|
26
|
+
}
|
|
27
|
+
// Get the current block hash for the branch
|
|
28
|
+
const block = await Tezos.rpc.getBlockHeader();
|
|
29
|
+
const branch = block.hash;
|
|
30
|
+
// Get the counter for the source account
|
|
31
|
+
const contractInfo = await Tezos.rpc.getContract(source);
|
|
32
|
+
if (!contractInfo) {
|
|
33
|
+
throw new Error(`Failed to get contract info for ${source}. Account may not exist on chain.`);
|
|
34
|
+
}
|
|
35
|
+
const nextCounter = (parseInt(contractInfo.counter || "0") + 1).toString();
|
|
36
|
+
// Build the transfer operation
|
|
37
|
+
const operation = {
|
|
38
|
+
branch,
|
|
39
|
+
contents: [
|
|
40
|
+
{
|
|
41
|
+
kind: OpKind.TRANSACTION,
|
|
42
|
+
source,
|
|
43
|
+
fee: "1500",
|
|
44
|
+
counter: nextCounter,
|
|
45
|
+
gas_limit: "1527",
|
|
46
|
+
storage_limit: "257",
|
|
47
|
+
amount: amount.toString(),
|
|
48
|
+
destination: recipient,
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
// Forge the operation using LocalForger
|
|
53
|
+
const forger = new LocalForger();
|
|
54
|
+
const forgedBytes = await forger.forge(operation);
|
|
55
|
+
// Sign the operation (with generic operation watermark 0x03)
|
|
56
|
+
const signature = await Tezos.signer.sign(forgedBytes, new Uint8Array([3]));
|
|
57
|
+
// Create x402 payload in the facilitator-compatible format
|
|
58
|
+
const payload = {
|
|
59
|
+
x402Version: 1,
|
|
60
|
+
scheme: "exact-tezos",
|
|
61
|
+
network,
|
|
62
|
+
asset: "XTZ",
|
|
63
|
+
payload: {
|
|
64
|
+
operationBytes: forgedBytes,
|
|
65
|
+
signature: signature.prefixSig,
|
|
66
|
+
publicKey,
|
|
67
|
+
source,
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
// Base64 encode the payload
|
|
71
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
72
|
+
return {
|
|
73
|
+
payload,
|
|
74
|
+
base64,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export declare const PaymentRequirementSchema: z.ZodObject<{
|
|
3
|
+
scheme: z.ZodString;
|
|
4
|
+
network: z.ZodString;
|
|
5
|
+
asset: z.ZodString;
|
|
6
|
+
amount: z.ZodString;
|
|
7
|
+
recipient: z.ZodString;
|
|
8
|
+
extra: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
decimals: z.ZodNumber;
|
|
11
|
+
}, z.z.core.$strip>>;
|
|
12
|
+
}, z.z.core.$strip>;
|
|
13
|
+
export declare const X402ResponseSchema: z.ZodObject<{
|
|
14
|
+
x402Version: z.ZodNumber;
|
|
15
|
+
paymentRequirements: z.ZodArray<z.ZodObject<{
|
|
16
|
+
scheme: z.ZodString;
|
|
17
|
+
network: z.ZodString;
|
|
18
|
+
asset: z.ZodString;
|
|
19
|
+
amount: z.ZodString;
|
|
20
|
+
recipient: z.ZodString;
|
|
21
|
+
extra: z.ZodOptional<z.ZodObject<{
|
|
22
|
+
name: z.ZodString;
|
|
23
|
+
decimals: z.ZodNumber;
|
|
24
|
+
}, z.z.core.$strip>>;
|
|
25
|
+
}, z.z.core.$strip>>;
|
|
26
|
+
}, z.z.core.$strip>;
|
|
27
|
+
export type PaymentRequirement = z.infer<typeof PaymentRequirementSchema>;
|
|
28
|
+
export type X402Response = z.infer<typeof X402ResponseSchema>;
|
|
29
|
+
export interface X402PaymentPayload {
|
|
30
|
+
x402Version: number;
|
|
31
|
+
scheme: "exact-tezos";
|
|
32
|
+
network: string;
|
|
33
|
+
asset: "XTZ";
|
|
34
|
+
payload: {
|
|
35
|
+
operationBytes: string;
|
|
36
|
+
signature: string;
|
|
37
|
+
publicKey: string;
|
|
38
|
+
source: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export const PaymentRequirementSchema = z.object({
|
|
3
|
+
scheme: z.string(),
|
|
4
|
+
network: z.string(),
|
|
5
|
+
asset: z.string(),
|
|
6
|
+
amount: z.string(),
|
|
7
|
+
recipient: z.string(),
|
|
8
|
+
extra: z.object({
|
|
9
|
+
name: z.string(),
|
|
10
|
+
decimals: z.number()
|
|
11
|
+
}).optional()
|
|
12
|
+
});
|
|
13
|
+
export const X402ResponseSchema = z.object({
|
|
14
|
+
x402Version: z.number(),
|
|
15
|
+
paymentRequirements: z.array(PaymentRequirementSchema)
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const startWebServer: (port: number) => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createServer } from "http";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import sirv from "sirv";
|
|
5
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
6
|
+
export const startWebServer = (port) => {
|
|
7
|
+
const distPath = join(__dirname, "../frontend/dist");
|
|
8
|
+
const serve = sirv(distPath, { single: true });
|
|
9
|
+
createServer(serve).listen(port);
|
|
10
|
+
};
|