@relai-fi/x402 0.6.5 → 0.6.7
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/README.md +251 -0
- package/dist/bridge.cjs +109 -0
- package/dist/bridge.cjs.map +1 -0
- package/dist/bridge.d.cts +78 -0
- package/dist/bridge.d.ts +78 -0
- package/dist/bridge.js +80 -0
- package/dist/bridge.js.map +1 -0
- package/dist/client.cjs +131 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +13 -1
- package/dist/client.d.ts +13 -1
- package/dist/client.js +131 -1
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +586 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +291 -30
- package/dist/index.d.ts +291 -30
- package/dist/index.js +578 -105
- package/dist/index.js.map +1 -1
- package/dist/mpp/bridge-client.cjs +23922 -0
- package/dist/mpp/bridge-client.cjs.map +1 -0
- package/dist/mpp/bridge-client.d.cts +58 -0
- package/dist/mpp/bridge-client.d.ts +58 -0
- package/dist/mpp/bridge-client.js +23892 -0
- package/dist/mpp/bridge-client.js.map +1 -0
- package/dist/mpp/bridge-method.cjs +13202 -0
- package/dist/mpp/bridge-method.cjs.map +1 -0
- package/dist/mpp/bridge-method.d.cts +69 -0
- package/dist/mpp/bridge-method.d.ts +69 -0
- package/dist/mpp/bridge-method.js +13181 -0
- package/dist/mpp/bridge-method.js.map +1 -0
- package/dist/mpp/bridge-server.cjs +13887 -0
- package/dist/mpp/bridge-server.cjs.map +1 -0
- package/dist/mpp/bridge-server.d.cts +62 -0
- package/dist/mpp/bridge-server.d.ts +62 -0
- package/dist/mpp/bridge-server.js +13866 -0
- package/dist/mpp/bridge-server.js.map +1 -0
- package/dist/mpp/evm-server.cjs +49 -33
- package/dist/mpp/evm-server.cjs.map +1 -1
- package/dist/mpp/evm-server.js +49 -33
- package/dist/mpp/evm-server.js.map +1 -1
- package/dist/mpp/verify-erc20.cjs +71 -0
- package/dist/mpp/verify-erc20.cjs.map +1 -0
- package/dist/mpp/verify-erc20.d.cts +27 -0
- package/dist/mpp/verify-erc20.d.ts +27 -0
- package/dist/mpp/verify-erc20.js +46 -0
- package/dist/mpp/verify-erc20.js.map +1 -0
- package/dist/mpp/verify-spl.cjs +96 -0
- package/dist/mpp/verify-spl.cjs.map +1 -0
- package/dist/mpp/verify-spl.d.cts +30 -0
- package/dist/mpp/verify-spl.d.ts +30 -0
- package/dist/mpp/verify-spl.js +71 -0
- package/dist/mpp/verify-spl.js.map +1 -0
- package/dist/mpp/with-bridge.cjs +23956 -0
- package/dist/mpp/with-bridge.cjs.map +1 -0
- package/dist/mpp/with-bridge.d.cts +53 -0
- package/dist/mpp/with-bridge.d.ts +53 -0
- package/dist/mpp/with-bridge.js +23926 -0
- package/dist/mpp/with-bridge.js.map +1 -0
- package/dist/plugins.d.cts +2 -2
- package/dist/plugins.d.ts +2 -2
- package/dist/react/index.cjs +131 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +131 -1
- package/dist/react/index.js.map +1 -1
- package/dist/{server-DaySqG5H.d.ts → server-D9ZfrFFx.d.ts} +1 -1
- package/dist/{server-CBZ2RjEP.d.cts → server-DgMG2zhy.d.cts} +1 -1
- package/dist/server.cjs +6 -39
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +6 -39
- package/dist/server.js.map +1 -1
- package/dist/{types-Y9ni5XwY.d.cts → types-DjEveKgt.d.cts} +1 -1
- package/dist/{types-Y9ni5XwY.d.ts → types-DjEveKgt.d.ts} +1 -1
- package/package.json +31 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/mpp/verify-erc20.ts
|
|
21
|
+
var verify_erc20_exports = {};
|
|
22
|
+
__export(verify_erc20_exports, {
|
|
23
|
+
verifyErc20Transfer: () => verifyErc20Transfer
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(verify_erc20_exports);
|
|
26
|
+
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
27
|
+
async function verifyErc20Transfer(opts) {
|
|
28
|
+
const { txHash, rpcUrl, tokenAddress, recipient, expectedAmount } = opts;
|
|
29
|
+
let receipt = null;
|
|
30
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
31
|
+
const receiptRes = await fetch(rpcUrl, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
jsonrpc: "2.0",
|
|
36
|
+
id: 1,
|
|
37
|
+
method: "eth_getTransactionReceipt",
|
|
38
|
+
params: [txHash]
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
const receiptData = await receiptRes.json();
|
|
42
|
+
if (receiptData.error) {
|
|
43
|
+
throw new Error(`RPC error: ${receiptData.error.message}`);
|
|
44
|
+
}
|
|
45
|
+
receipt = receiptData.result;
|
|
46
|
+
if (receipt) break;
|
|
47
|
+
await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
48
|
+
}
|
|
49
|
+
if (!receipt) {
|
|
50
|
+
throw new Error("Transaction not found or not yet confirmed");
|
|
51
|
+
}
|
|
52
|
+
if (receipt.status !== "0x1") {
|
|
53
|
+
throw new Error("Transaction failed on-chain");
|
|
54
|
+
}
|
|
55
|
+
const recipientPadded = "0x" + recipient.slice(2).toLowerCase().padStart(64, "0");
|
|
56
|
+
const tokenLower = tokenAddress.toLowerCase();
|
|
57
|
+
const matchingLog = receipt.logs.find((log) => {
|
|
58
|
+
if (log.address.toLowerCase() !== tokenLower) return false;
|
|
59
|
+
if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false;
|
|
60
|
+
if (log.topics[2]?.toLowerCase() !== recipientPadded) return false;
|
|
61
|
+
return BigInt(log.data) >= expectedAmount;
|
|
62
|
+
});
|
|
63
|
+
if (!matchingLog) {
|
|
64
|
+
throw new Error("No matching ERC-20 Transfer found for recipient and amount");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
68
|
+
0 && (module.exports = {
|
|
69
|
+
verifyErc20Transfer
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=verify-erc20.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mpp/verify-erc20.ts"],"sourcesContent":["/**\n * Shared ERC-20 Transfer verification utility.\n *\n * Used by both evm-server.ts (direct payments) and bridge-server.ts\n * (bridged payments on the target chain).\n */\n\nconst TRANSFER_EVENT_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'\n\nexport interface VerifyErc20TransferOptions {\n /** Transaction hash (0x-prefixed) */\n txHash: string\n /** RPC URL for the chain */\n rpcUrl: string\n /** ERC-20 token contract address */\n tokenAddress: string\n /** Expected recipient address */\n recipient: string\n /** Minimum expected amount in base units */\n expectedAmount: bigint\n}\n\n/**\n * Verify that an on-chain transaction contains a valid ERC-20 Transfer event\n * to the expected recipient for at least the expected amount.\n *\n * @throws if the transaction is missing, failed, or doesn't match.\n */\nexport async function verifyErc20Transfer(opts: VerifyErc20TransferOptions): Promise<void> {\n const { txHash, rpcUrl, tokenAddress, recipient, expectedAmount } = opts\n\n // Poll for receipt — the transaction may take a few seconds to be indexed by the RPC node\n let receipt: any = null\n for (let attempt = 0; attempt < 5; attempt++) {\n const receiptRes = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0', id: 1,\n method: 'eth_getTransactionReceipt',\n params: [txHash],\n }),\n })\n const receiptData = await receiptRes.json() as { result?: any; error?: { message: string } }\n\n if (receiptData.error) {\n throw new Error(`RPC error: ${receiptData.error.message}`)\n }\n\n receipt = receiptData.result\n if (receipt) break\n // Wait before retrying (1s, 2s, 3s, 4s)\n await new Promise((r) => setTimeout(r, (attempt + 1) * 1000))\n }\n\n if (!receipt) {\n throw new Error('Transaction not found or not yet confirmed')\n }\n if (receipt.status !== '0x1') {\n throw new Error('Transaction failed on-chain')\n }\n\n // Verify ERC-20 Transfer event\n const recipientPadded = '0x' + recipient.slice(2).toLowerCase().padStart(64, '0')\n const tokenLower = tokenAddress.toLowerCase()\n\n const matchingLog = (receipt.logs as any[]).find((log: any) => {\n if (log.address.toLowerCase() !== tokenLower) return false\n if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false\n if (log.topics[2]?.toLowerCase() !== recipientPadded) return false\n return BigInt(log.data) >= expectedAmount\n })\n\n if (!matchingLog) {\n throw new Error('No matching ERC-20 Transfer found for recipient and amount')\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAM,uBAAuB;AAqB7B,eAAsB,oBAAoB,MAAiD;AACzF,QAAM,EAAE,QAAQ,QAAQ,cAAc,WAAW,eAAe,IAAI;AAGpE,MAAI,UAAe;AACnB,WAAS,UAAU,GAAG,UAAU,GAAG,WAAW;AAC5C,UAAM,aAAa,MAAM,MAAM,QAAQ;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,SAAS;AAAA,QAAO,IAAI;AAAA,QACpB,QAAQ;AAAA,QACR,QAAQ,CAAC,MAAM;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AACD,UAAM,cAAc,MAAM,WAAW,KAAK;AAE1C,QAAI,YAAY,OAAO;AACrB,YAAM,IAAI,MAAM,cAAc,YAAY,MAAM,OAAO,EAAE;AAAA,IAC3D;AAEA,cAAU,YAAY;AACtB,QAAI,QAAS;AAEb,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,IAAI,UAAU,KAAK,GAAI,CAAC;AAAA,EAC9D;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,MAAI,QAAQ,WAAW,OAAO;AAC5B,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAGA,QAAM,kBAAkB,OAAO,UAAU,MAAM,CAAC,EAAE,YAAY,EAAE,SAAS,IAAI,GAAG;AAChF,QAAM,aAAa,aAAa,YAAY;AAE5C,QAAM,cAAe,QAAQ,KAAe,KAAK,CAAC,QAAa;AAC7D,QAAI,IAAI,QAAQ,YAAY,MAAM,WAAY,QAAO;AACrD,QAAI,IAAI,OAAO,CAAC,MAAM,qBAAsB,QAAO;AACnD,QAAI,IAAI,OAAO,CAAC,GAAG,YAAY,MAAM,gBAAiB,QAAO;AAC7D,WAAO,OAAO,IAAI,IAAI,KAAK;AAAA,EAC7B,CAAC;AAED,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AACF;","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ERC-20 Transfer verification utility.
|
|
3
|
+
*
|
|
4
|
+
* Used by both evm-server.ts (direct payments) and bridge-server.ts
|
|
5
|
+
* (bridged payments on the target chain).
|
|
6
|
+
*/
|
|
7
|
+
interface VerifyErc20TransferOptions {
|
|
8
|
+
/** Transaction hash (0x-prefixed) */
|
|
9
|
+
txHash: string;
|
|
10
|
+
/** RPC URL for the chain */
|
|
11
|
+
rpcUrl: string;
|
|
12
|
+
/** ERC-20 token contract address */
|
|
13
|
+
tokenAddress: string;
|
|
14
|
+
/** Expected recipient address */
|
|
15
|
+
recipient: string;
|
|
16
|
+
/** Minimum expected amount in base units */
|
|
17
|
+
expectedAmount: bigint;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verify that an on-chain transaction contains a valid ERC-20 Transfer event
|
|
21
|
+
* to the expected recipient for at least the expected amount.
|
|
22
|
+
*
|
|
23
|
+
* @throws if the transaction is missing, failed, or doesn't match.
|
|
24
|
+
*/
|
|
25
|
+
declare function verifyErc20Transfer(opts: VerifyErc20TransferOptions): Promise<void>;
|
|
26
|
+
|
|
27
|
+
export { type VerifyErc20TransferOptions, verifyErc20Transfer };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ERC-20 Transfer verification utility.
|
|
3
|
+
*
|
|
4
|
+
* Used by both evm-server.ts (direct payments) and bridge-server.ts
|
|
5
|
+
* (bridged payments on the target chain).
|
|
6
|
+
*/
|
|
7
|
+
interface VerifyErc20TransferOptions {
|
|
8
|
+
/** Transaction hash (0x-prefixed) */
|
|
9
|
+
txHash: string;
|
|
10
|
+
/** RPC URL for the chain */
|
|
11
|
+
rpcUrl: string;
|
|
12
|
+
/** ERC-20 token contract address */
|
|
13
|
+
tokenAddress: string;
|
|
14
|
+
/** Expected recipient address */
|
|
15
|
+
recipient: string;
|
|
16
|
+
/** Minimum expected amount in base units */
|
|
17
|
+
expectedAmount: bigint;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verify that an on-chain transaction contains a valid ERC-20 Transfer event
|
|
21
|
+
* to the expected recipient for at least the expected amount.
|
|
22
|
+
*
|
|
23
|
+
* @throws if the transaction is missing, failed, or doesn't match.
|
|
24
|
+
*/
|
|
25
|
+
declare function verifyErc20Transfer(opts: VerifyErc20TransferOptions): Promise<void>;
|
|
26
|
+
|
|
27
|
+
export { type VerifyErc20TransferOptions, verifyErc20Transfer };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/mpp/verify-erc20.ts
|
|
2
|
+
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
3
|
+
async function verifyErc20Transfer(opts) {
|
|
4
|
+
const { txHash, rpcUrl, tokenAddress, recipient, expectedAmount } = opts;
|
|
5
|
+
let receipt = null;
|
|
6
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
7
|
+
const receiptRes = await fetch(rpcUrl, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: { "Content-Type": "application/json" },
|
|
10
|
+
body: JSON.stringify({
|
|
11
|
+
jsonrpc: "2.0",
|
|
12
|
+
id: 1,
|
|
13
|
+
method: "eth_getTransactionReceipt",
|
|
14
|
+
params: [txHash]
|
|
15
|
+
})
|
|
16
|
+
});
|
|
17
|
+
const receiptData = await receiptRes.json();
|
|
18
|
+
if (receiptData.error) {
|
|
19
|
+
throw new Error(`RPC error: ${receiptData.error.message}`);
|
|
20
|
+
}
|
|
21
|
+
receipt = receiptData.result;
|
|
22
|
+
if (receipt) break;
|
|
23
|
+
await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
24
|
+
}
|
|
25
|
+
if (!receipt) {
|
|
26
|
+
throw new Error("Transaction not found or not yet confirmed");
|
|
27
|
+
}
|
|
28
|
+
if (receipt.status !== "0x1") {
|
|
29
|
+
throw new Error("Transaction failed on-chain");
|
|
30
|
+
}
|
|
31
|
+
const recipientPadded = "0x" + recipient.slice(2).toLowerCase().padStart(64, "0");
|
|
32
|
+
const tokenLower = tokenAddress.toLowerCase();
|
|
33
|
+
const matchingLog = receipt.logs.find((log) => {
|
|
34
|
+
if (log.address.toLowerCase() !== tokenLower) return false;
|
|
35
|
+
if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false;
|
|
36
|
+
if (log.topics[2]?.toLowerCase() !== recipientPadded) return false;
|
|
37
|
+
return BigInt(log.data) >= expectedAmount;
|
|
38
|
+
});
|
|
39
|
+
if (!matchingLog) {
|
|
40
|
+
throw new Error("No matching ERC-20 Transfer found for recipient and amount");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
verifyErc20Transfer
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=verify-erc20.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mpp/verify-erc20.ts"],"sourcesContent":["/**\n * Shared ERC-20 Transfer verification utility.\n *\n * Used by both evm-server.ts (direct payments) and bridge-server.ts\n * (bridged payments on the target chain).\n */\n\nconst TRANSFER_EVENT_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'\n\nexport interface VerifyErc20TransferOptions {\n /** Transaction hash (0x-prefixed) */\n txHash: string\n /** RPC URL for the chain */\n rpcUrl: string\n /** ERC-20 token contract address */\n tokenAddress: string\n /** Expected recipient address */\n recipient: string\n /** Minimum expected amount in base units */\n expectedAmount: bigint\n}\n\n/**\n * Verify that an on-chain transaction contains a valid ERC-20 Transfer event\n * to the expected recipient for at least the expected amount.\n *\n * @throws if the transaction is missing, failed, or doesn't match.\n */\nexport async function verifyErc20Transfer(opts: VerifyErc20TransferOptions): Promise<void> {\n const { txHash, rpcUrl, tokenAddress, recipient, expectedAmount } = opts\n\n // Poll for receipt — the transaction may take a few seconds to be indexed by the RPC node\n let receipt: any = null\n for (let attempt = 0; attempt < 5; attempt++) {\n const receiptRes = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0', id: 1,\n method: 'eth_getTransactionReceipt',\n params: [txHash],\n }),\n })\n const receiptData = await receiptRes.json() as { result?: any; error?: { message: string } }\n\n if (receiptData.error) {\n throw new Error(`RPC error: ${receiptData.error.message}`)\n }\n\n receipt = receiptData.result\n if (receipt) break\n // Wait before retrying (1s, 2s, 3s, 4s)\n await new Promise((r) => setTimeout(r, (attempt + 1) * 1000))\n }\n\n if (!receipt) {\n throw new Error('Transaction not found or not yet confirmed')\n }\n if (receipt.status !== '0x1') {\n throw new Error('Transaction failed on-chain')\n }\n\n // Verify ERC-20 Transfer event\n const recipientPadded = '0x' + recipient.slice(2).toLowerCase().padStart(64, '0')\n const tokenLower = tokenAddress.toLowerCase()\n\n const matchingLog = (receipt.logs as any[]).find((log: any) => {\n if (log.address.toLowerCase() !== tokenLower) return false\n if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false\n if (log.topics[2]?.toLowerCase() !== recipientPadded) return false\n return BigInt(log.data) >= expectedAmount\n })\n\n if (!matchingLog) {\n throw new Error('No matching ERC-20 Transfer found for recipient and amount')\n }\n}\n"],"mappings":";AAOA,IAAM,uBAAuB;AAqB7B,eAAsB,oBAAoB,MAAiD;AACzF,QAAM,EAAE,QAAQ,QAAQ,cAAc,WAAW,eAAe,IAAI;AAGpE,MAAI,UAAe;AACnB,WAAS,UAAU,GAAG,UAAU,GAAG,WAAW;AAC5C,UAAM,aAAa,MAAM,MAAM,QAAQ;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,SAAS;AAAA,QAAO,IAAI;AAAA,QACpB,QAAQ;AAAA,QACR,QAAQ,CAAC,MAAM;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AACD,UAAM,cAAc,MAAM,WAAW,KAAK;AAE1C,QAAI,YAAY,OAAO;AACrB,YAAM,IAAI,MAAM,cAAc,YAAY,MAAM,OAAO,EAAE;AAAA,IAC3D;AAEA,cAAU,YAAY;AACtB,QAAI,QAAS;AAEb,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,IAAI,UAAU,KAAK,GAAI,CAAC;AAAA,EAC9D;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,MAAI,QAAQ,WAAW,OAAO;AAC5B,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAGA,QAAM,kBAAkB,OAAO,UAAU,MAAM,CAAC,EAAE,YAAY,EAAE,SAAS,IAAI,GAAG;AAChF,QAAM,aAAa,aAAa,YAAY;AAE5C,QAAM,cAAe,QAAQ,KAAe,KAAK,CAAC,QAAa;AAC7D,QAAI,IAAI,QAAQ,YAAY,MAAM,WAAY,QAAO;AACrD,QAAI,IAAI,OAAO,CAAC,MAAM,qBAAsB,QAAO;AACnD,QAAI,IAAI,OAAO,CAAC,GAAG,YAAY,MAAM,gBAAiB,QAAO;AAC7D,WAAO,OAAO,IAAI,IAAI,KAAK;AAAA,EAC7B,CAAC;AAED,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AACF;","names":[]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/mpp/verify-spl.ts
|
|
21
|
+
var verify_spl_exports = {};
|
|
22
|
+
__export(verify_spl_exports, {
|
|
23
|
+
verifySplTransfer: () => verifySplTransfer
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(verify_spl_exports);
|
|
26
|
+
async function verifySplTransfer(opts) {
|
|
27
|
+
const { txSignature, rpcUrl, tokenMint, recipient, expectedAmount } = opts;
|
|
28
|
+
const res = await fetch(rpcUrl, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
jsonrpc: "2.0",
|
|
33
|
+
id: 1,
|
|
34
|
+
method: "getTransaction",
|
|
35
|
+
params: [
|
|
36
|
+
txSignature,
|
|
37
|
+
{ encoding: "jsonParsed", commitment: "confirmed", maxSupportedTransactionVersion: 0 }
|
|
38
|
+
]
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
if (data.error) {
|
|
43
|
+
throw new Error(`Solana RPC error: ${data.error.message}`);
|
|
44
|
+
}
|
|
45
|
+
const tx = data.result;
|
|
46
|
+
if (!tx) {
|
|
47
|
+
throw new Error("Solana transaction not found or not yet confirmed");
|
|
48
|
+
}
|
|
49
|
+
if (tx.meta?.err) {
|
|
50
|
+
throw new Error(`Solana transaction failed: ${JSON.stringify(tx.meta.err)}`);
|
|
51
|
+
}
|
|
52
|
+
const allInstructions = [
|
|
53
|
+
...tx.transaction?.message?.instructions || [],
|
|
54
|
+
...tx.meta?.innerInstructions?.flatMap((inner) => inner.instructions) || []
|
|
55
|
+
];
|
|
56
|
+
const mintLower = tokenMint.toLowerCase();
|
|
57
|
+
const recipientLower = recipient.toLowerCase();
|
|
58
|
+
const matched = allInstructions.some((ix) => {
|
|
59
|
+
const parsed = ix.parsed;
|
|
60
|
+
if (!parsed) return false;
|
|
61
|
+
const type = parsed.type;
|
|
62
|
+
if (type !== "transfer" && type !== "transferChecked") return false;
|
|
63
|
+
const info = parsed.info;
|
|
64
|
+
if (!info) return false;
|
|
65
|
+
if (type === "transferChecked") {
|
|
66
|
+
if (info.mint?.toLowerCase() !== mintLower) return false;
|
|
67
|
+
}
|
|
68
|
+
const amount = type === "transferChecked" ? BigInt(info.tokenAmount?.amount || "0") : BigInt(info.amount || "0");
|
|
69
|
+
if (amount < expectedAmount) return false;
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
const postBalances = tx.meta?.postTokenBalances || [];
|
|
73
|
+
const preBalances = tx.meta?.preTokenBalances || [];
|
|
74
|
+
const recipientPostBalance = postBalances.find(
|
|
75
|
+
(b) => b.mint?.toLowerCase() === mintLower && b.owner?.toLowerCase() === recipientLower
|
|
76
|
+
);
|
|
77
|
+
if (!recipientPostBalance) {
|
|
78
|
+
throw new Error("Recipient not found in post-token balances for the expected mint");
|
|
79
|
+
}
|
|
80
|
+
const recipientPreBalance = preBalances.find(
|
|
81
|
+
(b) => b.mint?.toLowerCase() === mintLower && b.owner?.toLowerCase() === recipientLower
|
|
82
|
+
);
|
|
83
|
+
const postAmount = BigInt(recipientPostBalance.uiTokenAmount?.amount || "0");
|
|
84
|
+
const preAmount = BigInt(recipientPreBalance?.uiTokenAmount?.amount || "0");
|
|
85
|
+
const received = postAmount - preAmount;
|
|
86
|
+
if (received < expectedAmount) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Insufficient SPL transfer: expected ${expectedAmount}, recipient received ${received}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
93
|
+
0 && (module.exports = {
|
|
94
|
+
verifySplTransfer
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=verify-spl.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mpp/verify-spl.ts"],"sourcesContent":["/**\n * Shared SPL Token Transfer verification utility for Solana.\n *\n * Used by bridge-server.ts when the target chain is Solana.\n * Verifies that a transaction contains an SPL token transfer\n * to the expected recipient for at least the expected amount.\n */\n\nexport interface VerifySplTransferOptions {\n /** Transaction signature (base58) */\n txSignature: string\n /** Solana RPC URL */\n rpcUrl: string\n /** SPL token mint address */\n tokenMint: string\n /** Expected recipient wallet address */\n recipient: string\n /** Minimum expected amount in base units */\n expectedAmount: bigint\n}\n\n/**\n * Verify that a Solana transaction contains a valid SPL token transfer\n * to the expected recipient for at least the expected amount.\n *\n * Uses raw JSON-RPC to avoid hard dependency on @solana/web3.js at runtime.\n *\n * @throws if the transaction is missing, failed, or doesn't match.\n */\nexport async function verifySplTransfer(opts: VerifySplTransferOptions): Promise<void> {\n const { txSignature, rpcUrl, tokenMint, recipient, expectedAmount } = opts\n\n // Fetch parsed transaction (includes token transfer details)\n const res = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTransaction',\n params: [\n txSignature,\n { encoding: 'jsonParsed', commitment: 'confirmed', maxSupportedTransactionVersion: 0 },\n ],\n }),\n })\n\n const data = (await res.json()) as { result?: any; error?: { message: string } }\n\n if (data.error) {\n throw new Error(`Solana RPC error: ${data.error.message}`)\n }\n\n const tx = data.result\n if (!tx) {\n throw new Error('Solana transaction not found or not yet confirmed')\n }\n\n if (tx.meta?.err) {\n throw new Error(`Solana transaction failed: ${JSON.stringify(tx.meta.err)}`)\n }\n\n // Look for matching SPL token transfer in inner + outer instructions\n const allInstructions = [\n ...(tx.transaction?.message?.instructions || []),\n ...(tx.meta?.innerInstructions?.flatMap((inner: any) => inner.instructions) || []),\n ]\n\n const mintLower = tokenMint.toLowerCase()\n const recipientLower = recipient.toLowerCase()\n\n const matched = allInstructions.some((ix: any) => {\n const parsed = ix.parsed\n if (!parsed) return false\n\n // SPL Token transfer / transferChecked\n const type = parsed.type\n if (type !== 'transfer' && type !== 'transferChecked') return false\n\n const info = parsed.info\n if (!info) return false\n\n // Check mint (transferChecked has .mint, transfer we check via token balances)\n if (type === 'transferChecked') {\n if (info.mint?.toLowerCase() !== mintLower) return false\n }\n\n // Check amount\n const amount = type === 'transferChecked'\n ? BigInt(info.tokenAmount?.amount || '0')\n : BigInt(info.amount || '0')\n\n if (amount < expectedAmount) return false\n\n // Check destination — for SPL, 'destination' is an ATA, not the wallet directly.\n // We need to check postTokenBalances to see if recipient's wallet received tokens.\n return true\n })\n\n // Also verify via postTokenBalances that recipient received tokens\n const postBalances: any[] = tx.meta?.postTokenBalances || []\n const preBalances: any[] = tx.meta?.preTokenBalances || []\n\n const recipientPostBalance = postBalances.find(\n (b: any) =>\n b.mint?.toLowerCase() === mintLower &&\n b.owner?.toLowerCase() === recipientLower,\n )\n\n if (!recipientPostBalance) {\n throw new Error('Recipient not found in post-token balances for the expected mint')\n }\n\n const recipientPreBalance = preBalances.find(\n (b: any) =>\n b.mint?.toLowerCase() === mintLower &&\n b.owner?.toLowerCase() === recipientLower,\n )\n\n const postAmount = BigInt(recipientPostBalance.uiTokenAmount?.amount || '0')\n const preAmount = BigInt(recipientPreBalance?.uiTokenAmount?.amount || '0')\n const received = postAmount - preAmount\n\n if (received < expectedAmount) {\n throw new Error(\n `Insufficient SPL transfer: expected ${expectedAmount}, recipient received ${received}`,\n )\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA6BA,eAAsB,kBAAkB,MAA+C;AACrF,QAAM,EAAE,aAAa,QAAQ,WAAW,WAAW,eAAe,IAAI;AAGtE,QAAM,MAAM,MAAM,MAAM,QAAQ;AAAA,IAC9B,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,SAAS;AAAA,MACT,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,QACN;AAAA,QACA,EAAE,UAAU,cAAc,YAAY,aAAa,gCAAgC,EAAE;AAAA,MACvF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,KAAK,OAAO;AACd,UAAM,IAAI,MAAM,qBAAqB,KAAK,MAAM,OAAO,EAAE;AAAA,EAC3D;AAEA,QAAM,KAAK,KAAK;AAChB,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAEA,MAAI,GAAG,MAAM,KAAK;AAChB,UAAM,IAAI,MAAM,8BAA8B,KAAK,UAAU,GAAG,KAAK,GAAG,CAAC,EAAE;AAAA,EAC7E;AAGA,QAAM,kBAAkB;AAAA,IACtB,GAAI,GAAG,aAAa,SAAS,gBAAgB,CAAC;AAAA,IAC9C,GAAI,GAAG,MAAM,mBAAmB,QAAQ,CAAC,UAAe,MAAM,YAAY,KAAK,CAAC;AAAA,EAClF;AAEA,QAAM,YAAY,UAAU,YAAY;AACxC,QAAM,iBAAiB,UAAU,YAAY;AAE7C,QAAM,UAAU,gBAAgB,KAAK,CAAC,OAAY;AAChD,UAAM,SAAS,GAAG;AAClB,QAAI,CAAC,OAAQ,QAAO;AAGpB,UAAM,OAAO,OAAO;AACpB,QAAI,SAAS,cAAc,SAAS,kBAAmB,QAAO;AAE9D,UAAM,OAAO,OAAO;AACpB,QAAI,CAAC,KAAM,QAAO;AAGlB,QAAI,SAAS,mBAAmB;AAC9B,UAAI,KAAK,MAAM,YAAY,MAAM,UAAW,QAAO;AAAA,IACrD;AAGA,UAAM,SAAS,SAAS,oBACpB,OAAO,KAAK,aAAa,UAAU,GAAG,IACtC,OAAO,KAAK,UAAU,GAAG;AAE7B,QAAI,SAAS,eAAgB,QAAO;AAIpC,WAAO;AAAA,EACT,CAAC;AAGD,QAAM,eAAsB,GAAG,MAAM,qBAAqB,CAAC;AAC3D,QAAM,cAAqB,GAAG,MAAM,oBAAoB,CAAC;AAEzD,QAAM,uBAAuB,aAAa;AAAA,IACxC,CAAC,MACC,EAAE,MAAM,YAAY,MAAM,aAC1B,EAAE,OAAO,YAAY,MAAM;AAAA,EAC/B;AAEA,MAAI,CAAC,sBAAsB;AACzB,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AAEA,QAAM,sBAAsB,YAAY;AAAA,IACtC,CAAC,MACC,EAAE,MAAM,YAAY,MAAM,aAC1B,EAAE,OAAO,YAAY,MAAM;AAAA,EAC/B;AAEA,QAAM,aAAa,OAAO,qBAAqB,eAAe,UAAU,GAAG;AAC3E,QAAM,YAAY,OAAO,qBAAqB,eAAe,UAAU,GAAG;AAC1E,QAAM,WAAW,aAAa;AAE9B,MAAI,WAAW,gBAAgB;AAC7B,UAAM,IAAI;AAAA,MACR,uCAAuC,cAAc,wBAAwB,QAAQ;AAAA,IACvF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SPL Token Transfer verification utility for Solana.
|
|
3
|
+
*
|
|
4
|
+
* Used by bridge-server.ts when the target chain is Solana.
|
|
5
|
+
* Verifies that a transaction contains an SPL token transfer
|
|
6
|
+
* to the expected recipient for at least the expected amount.
|
|
7
|
+
*/
|
|
8
|
+
interface VerifySplTransferOptions {
|
|
9
|
+
/** Transaction signature (base58) */
|
|
10
|
+
txSignature: string;
|
|
11
|
+
/** Solana RPC URL */
|
|
12
|
+
rpcUrl: string;
|
|
13
|
+
/** SPL token mint address */
|
|
14
|
+
tokenMint: string;
|
|
15
|
+
/** Expected recipient wallet address */
|
|
16
|
+
recipient: string;
|
|
17
|
+
/** Minimum expected amount in base units */
|
|
18
|
+
expectedAmount: bigint;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify that a Solana transaction contains a valid SPL token transfer
|
|
22
|
+
* to the expected recipient for at least the expected amount.
|
|
23
|
+
*
|
|
24
|
+
* Uses raw JSON-RPC to avoid hard dependency on @solana/web3.js at runtime.
|
|
25
|
+
*
|
|
26
|
+
* @throws if the transaction is missing, failed, or doesn't match.
|
|
27
|
+
*/
|
|
28
|
+
declare function verifySplTransfer(opts: VerifySplTransferOptions): Promise<void>;
|
|
29
|
+
|
|
30
|
+
export { type VerifySplTransferOptions, verifySplTransfer };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SPL Token Transfer verification utility for Solana.
|
|
3
|
+
*
|
|
4
|
+
* Used by bridge-server.ts when the target chain is Solana.
|
|
5
|
+
* Verifies that a transaction contains an SPL token transfer
|
|
6
|
+
* to the expected recipient for at least the expected amount.
|
|
7
|
+
*/
|
|
8
|
+
interface VerifySplTransferOptions {
|
|
9
|
+
/** Transaction signature (base58) */
|
|
10
|
+
txSignature: string;
|
|
11
|
+
/** Solana RPC URL */
|
|
12
|
+
rpcUrl: string;
|
|
13
|
+
/** SPL token mint address */
|
|
14
|
+
tokenMint: string;
|
|
15
|
+
/** Expected recipient wallet address */
|
|
16
|
+
recipient: string;
|
|
17
|
+
/** Minimum expected amount in base units */
|
|
18
|
+
expectedAmount: bigint;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify that a Solana transaction contains a valid SPL token transfer
|
|
22
|
+
* to the expected recipient for at least the expected amount.
|
|
23
|
+
*
|
|
24
|
+
* Uses raw JSON-RPC to avoid hard dependency on @solana/web3.js at runtime.
|
|
25
|
+
*
|
|
26
|
+
* @throws if the transaction is missing, failed, or doesn't match.
|
|
27
|
+
*/
|
|
28
|
+
declare function verifySplTransfer(opts: VerifySplTransferOptions): Promise<void>;
|
|
29
|
+
|
|
30
|
+
export { type VerifySplTransferOptions, verifySplTransfer };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/mpp/verify-spl.ts
|
|
2
|
+
async function verifySplTransfer(opts) {
|
|
3
|
+
const { txSignature, rpcUrl, tokenMint, recipient, expectedAmount } = opts;
|
|
4
|
+
const res = await fetch(rpcUrl, {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: { "Content-Type": "application/json" },
|
|
7
|
+
body: JSON.stringify({
|
|
8
|
+
jsonrpc: "2.0",
|
|
9
|
+
id: 1,
|
|
10
|
+
method: "getTransaction",
|
|
11
|
+
params: [
|
|
12
|
+
txSignature,
|
|
13
|
+
{ encoding: "jsonParsed", commitment: "confirmed", maxSupportedTransactionVersion: 0 }
|
|
14
|
+
]
|
|
15
|
+
})
|
|
16
|
+
});
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
if (data.error) {
|
|
19
|
+
throw new Error(`Solana RPC error: ${data.error.message}`);
|
|
20
|
+
}
|
|
21
|
+
const tx = data.result;
|
|
22
|
+
if (!tx) {
|
|
23
|
+
throw new Error("Solana transaction not found or not yet confirmed");
|
|
24
|
+
}
|
|
25
|
+
if (tx.meta?.err) {
|
|
26
|
+
throw new Error(`Solana transaction failed: ${JSON.stringify(tx.meta.err)}`);
|
|
27
|
+
}
|
|
28
|
+
const allInstructions = [
|
|
29
|
+
...tx.transaction?.message?.instructions || [],
|
|
30
|
+
...tx.meta?.innerInstructions?.flatMap((inner) => inner.instructions) || []
|
|
31
|
+
];
|
|
32
|
+
const mintLower = tokenMint.toLowerCase();
|
|
33
|
+
const recipientLower = recipient.toLowerCase();
|
|
34
|
+
const matched = allInstructions.some((ix) => {
|
|
35
|
+
const parsed = ix.parsed;
|
|
36
|
+
if (!parsed) return false;
|
|
37
|
+
const type = parsed.type;
|
|
38
|
+
if (type !== "transfer" && type !== "transferChecked") return false;
|
|
39
|
+
const info = parsed.info;
|
|
40
|
+
if (!info) return false;
|
|
41
|
+
if (type === "transferChecked") {
|
|
42
|
+
if (info.mint?.toLowerCase() !== mintLower) return false;
|
|
43
|
+
}
|
|
44
|
+
const amount = type === "transferChecked" ? BigInt(info.tokenAmount?.amount || "0") : BigInt(info.amount || "0");
|
|
45
|
+
if (amount < expectedAmount) return false;
|
|
46
|
+
return true;
|
|
47
|
+
});
|
|
48
|
+
const postBalances = tx.meta?.postTokenBalances || [];
|
|
49
|
+
const preBalances = tx.meta?.preTokenBalances || [];
|
|
50
|
+
const recipientPostBalance = postBalances.find(
|
|
51
|
+
(b) => b.mint?.toLowerCase() === mintLower && b.owner?.toLowerCase() === recipientLower
|
|
52
|
+
);
|
|
53
|
+
if (!recipientPostBalance) {
|
|
54
|
+
throw new Error("Recipient not found in post-token balances for the expected mint");
|
|
55
|
+
}
|
|
56
|
+
const recipientPreBalance = preBalances.find(
|
|
57
|
+
(b) => b.mint?.toLowerCase() === mintLower && b.owner?.toLowerCase() === recipientLower
|
|
58
|
+
);
|
|
59
|
+
const postAmount = BigInt(recipientPostBalance.uiTokenAmount?.amount || "0");
|
|
60
|
+
const preAmount = BigInt(recipientPreBalance?.uiTokenAmount?.amount || "0");
|
|
61
|
+
const received = postAmount - preAmount;
|
|
62
|
+
if (received < expectedAmount) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Insufficient SPL transfer: expected ${expectedAmount}, recipient received ${received}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
verifySplTransfer
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=verify-spl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mpp/verify-spl.ts"],"sourcesContent":["/**\n * Shared SPL Token Transfer verification utility for Solana.\n *\n * Used by bridge-server.ts when the target chain is Solana.\n * Verifies that a transaction contains an SPL token transfer\n * to the expected recipient for at least the expected amount.\n */\n\nexport interface VerifySplTransferOptions {\n /** Transaction signature (base58) */\n txSignature: string\n /** Solana RPC URL */\n rpcUrl: string\n /** SPL token mint address */\n tokenMint: string\n /** Expected recipient wallet address */\n recipient: string\n /** Minimum expected amount in base units */\n expectedAmount: bigint\n}\n\n/**\n * Verify that a Solana transaction contains a valid SPL token transfer\n * to the expected recipient for at least the expected amount.\n *\n * Uses raw JSON-RPC to avoid hard dependency on @solana/web3.js at runtime.\n *\n * @throws if the transaction is missing, failed, or doesn't match.\n */\nexport async function verifySplTransfer(opts: VerifySplTransferOptions): Promise<void> {\n const { txSignature, rpcUrl, tokenMint, recipient, expectedAmount } = opts\n\n // Fetch parsed transaction (includes token transfer details)\n const res = await fetch(rpcUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: 1,\n method: 'getTransaction',\n params: [\n txSignature,\n { encoding: 'jsonParsed', commitment: 'confirmed', maxSupportedTransactionVersion: 0 },\n ],\n }),\n })\n\n const data = (await res.json()) as { result?: any; error?: { message: string } }\n\n if (data.error) {\n throw new Error(`Solana RPC error: ${data.error.message}`)\n }\n\n const tx = data.result\n if (!tx) {\n throw new Error('Solana transaction not found or not yet confirmed')\n }\n\n if (tx.meta?.err) {\n throw new Error(`Solana transaction failed: ${JSON.stringify(tx.meta.err)}`)\n }\n\n // Look for matching SPL token transfer in inner + outer instructions\n const allInstructions = [\n ...(tx.transaction?.message?.instructions || []),\n ...(tx.meta?.innerInstructions?.flatMap((inner: any) => inner.instructions) || []),\n ]\n\n const mintLower = tokenMint.toLowerCase()\n const recipientLower = recipient.toLowerCase()\n\n const matched = allInstructions.some((ix: any) => {\n const parsed = ix.parsed\n if (!parsed) return false\n\n // SPL Token transfer / transferChecked\n const type = parsed.type\n if (type !== 'transfer' && type !== 'transferChecked') return false\n\n const info = parsed.info\n if (!info) return false\n\n // Check mint (transferChecked has .mint, transfer we check via token balances)\n if (type === 'transferChecked') {\n if (info.mint?.toLowerCase() !== mintLower) return false\n }\n\n // Check amount\n const amount = type === 'transferChecked'\n ? BigInt(info.tokenAmount?.amount || '0')\n : BigInt(info.amount || '0')\n\n if (amount < expectedAmount) return false\n\n // Check destination — for SPL, 'destination' is an ATA, not the wallet directly.\n // We need to check postTokenBalances to see if recipient's wallet received tokens.\n return true\n })\n\n // Also verify via postTokenBalances that recipient received tokens\n const postBalances: any[] = tx.meta?.postTokenBalances || []\n const preBalances: any[] = tx.meta?.preTokenBalances || []\n\n const recipientPostBalance = postBalances.find(\n (b: any) =>\n b.mint?.toLowerCase() === mintLower &&\n b.owner?.toLowerCase() === recipientLower,\n )\n\n if (!recipientPostBalance) {\n throw new Error('Recipient not found in post-token balances for the expected mint')\n }\n\n const recipientPreBalance = preBalances.find(\n (b: any) =>\n b.mint?.toLowerCase() === mintLower &&\n b.owner?.toLowerCase() === recipientLower,\n )\n\n const postAmount = BigInt(recipientPostBalance.uiTokenAmount?.amount || '0')\n const preAmount = BigInt(recipientPreBalance?.uiTokenAmount?.amount || '0')\n const received = postAmount - preAmount\n\n if (received < expectedAmount) {\n throw new Error(\n `Insufficient SPL transfer: expected ${expectedAmount}, recipient received ${received}`,\n )\n }\n}\n"],"mappings":";AA6BA,eAAsB,kBAAkB,MAA+C;AACrF,QAAM,EAAE,aAAa,QAAQ,WAAW,WAAW,eAAe,IAAI;AAGtE,QAAM,MAAM,MAAM,MAAM,QAAQ;AAAA,IAC9B,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,SAAS;AAAA,MACT,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,QACN;AAAA,QACA,EAAE,UAAU,cAAc,YAAY,aAAa,gCAAgC,EAAE;AAAA,MACvF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,KAAK,OAAO;AACd,UAAM,IAAI,MAAM,qBAAqB,KAAK,MAAM,OAAO,EAAE;AAAA,EAC3D;AAEA,QAAM,KAAK,KAAK;AAChB,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AAEA,MAAI,GAAG,MAAM,KAAK;AAChB,UAAM,IAAI,MAAM,8BAA8B,KAAK,UAAU,GAAG,KAAK,GAAG,CAAC,EAAE;AAAA,EAC7E;AAGA,QAAM,kBAAkB;AAAA,IACtB,GAAI,GAAG,aAAa,SAAS,gBAAgB,CAAC;AAAA,IAC9C,GAAI,GAAG,MAAM,mBAAmB,QAAQ,CAAC,UAAe,MAAM,YAAY,KAAK,CAAC;AAAA,EAClF;AAEA,QAAM,YAAY,UAAU,YAAY;AACxC,QAAM,iBAAiB,UAAU,YAAY;AAE7C,QAAM,UAAU,gBAAgB,KAAK,CAAC,OAAY;AAChD,UAAM,SAAS,GAAG;AAClB,QAAI,CAAC,OAAQ,QAAO;AAGpB,UAAM,OAAO,OAAO;AACpB,QAAI,SAAS,cAAc,SAAS,kBAAmB,QAAO;AAE9D,UAAM,OAAO,OAAO;AACpB,QAAI,CAAC,KAAM,QAAO;AAGlB,QAAI,SAAS,mBAAmB;AAC9B,UAAI,KAAK,MAAM,YAAY,MAAM,UAAW,QAAO;AAAA,IACrD;AAGA,UAAM,SAAS,SAAS,oBACpB,OAAO,KAAK,aAAa,UAAU,GAAG,IACtC,OAAO,KAAK,UAAU,GAAG;AAE7B,QAAI,SAAS,eAAgB,QAAO;AAIpC,WAAO;AAAA,EACT,CAAC;AAGD,QAAM,eAAsB,GAAG,MAAM,qBAAqB,CAAC;AAC3D,QAAM,cAAqB,GAAG,MAAM,oBAAoB,CAAC;AAEzD,QAAM,uBAAuB,aAAa;AAAA,IACxC,CAAC,MACC,EAAE,MAAM,YAAY,MAAM,aAC1B,EAAE,OAAO,YAAY,MAAM;AAAA,EAC/B;AAEA,MAAI,CAAC,sBAAsB;AACzB,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AAEA,QAAM,sBAAsB,YAAY;AAAA,IACtC,CAAC,MACC,EAAE,MAAM,YAAY,MAAM,aAC1B,EAAE,OAAO,YAAY,MAAM;AAAA,EAC/B;AAEA,QAAM,aAAa,OAAO,qBAAqB,eAAe,UAAU,GAAG;AAC3E,QAAM,YAAY,OAAO,qBAAqB,eAAe,UAAU,GAAG;AAC1E,QAAM,WAAW,aAAa;AAE9B,MAAI,WAAW,gBAAgB;AAC7B,UAAM,IAAI;AAAA,MACR,uCAAuC,cAAc,wBAAwB,QAAQ;AAAA,IACvF;AAAA,EACF;AACF;","names":[]}
|