@faremeter/payment-solana 0.19.0 → 0.20.1
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 +70 -0
- package/dist/src/charge/client.d.ts +18 -0
- package/dist/src/charge/client.d.ts.map +1 -0
- package/dist/src/charge/client.js +205 -0
- package/dist/src/charge/common.d.ts +44 -0
- package/dist/src/charge/common.d.ts.map +1 -0
- package/dist/src/charge/common.js +29 -0
- package/dist/src/charge/index.d.ts +7 -0
- package/dist/src/charge/index.d.ts.map +1 -0
- package/dist/src/charge/index.js +3 -0
- package/dist/src/charge/logger.d.ts +2 -0
- package/dist/src/charge/logger.d.ts.map +1 -0
- package/dist/src/charge/logger.js +2 -0
- package/dist/src/charge/replay.d.ts +12 -0
- package/dist/src/charge/replay.d.ts.map +1 -0
- package/dist/src/charge/replay.js +24 -0
- package/dist/src/charge/server.d.ts +31 -0
- package/dist/src/charge/server.d.ts.map +1 -0
- package/dist/src/charge/server.js +395 -0
- package/dist/src/charge/verify.d.ts +40 -0
- package/dist/src/charge/verify.d.ts.map +1 -0
- package/dist/src/charge/verify.js +185 -0
- package/dist/src/common.d.ts +12 -0
- package/dist/src/common.d.ts.map +1 -0
- package/dist/src/common.js +1 -0
- package/dist/src/exact/client.d.ts.map +1 -1
- package/dist/src/exact/client.js +5 -2
- package/dist/src/exact/facilitator.d.ts +1 -0
- package/dist/src/exact/facilitator.d.ts.map +1 -1
- package/dist/src/exact/facilitator.js +6 -0
- package/dist/src/exact/index.d.ts +1 -1
- package/dist/src/exact/index.d.ts.map +1 -1
- package/dist/src/exact/memo.d.ts +4 -0
- package/dist/src/exact/memo.d.ts.map +1 -0
- package/dist/src/exact/memo.js +16 -0
- package/dist/src/exact/verify.d.ts +2 -1
- package/dist/src/exact/verify.d.ts.map +1 -1
- package/dist/src/exact/verify.js +48 -4
- package/dist/src/exact/verify.test.js +155 -16
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +21 -15
package/dist/src/exact/client.js
CHANGED
|
@@ -4,6 +4,7 @@ import { getBase64EncodedWireTransaction, } from "@solana/transactions";
|
|
|
4
4
|
import { ComputeBudgetProgram, Connection, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction, Keypair, } from "@solana/web3.js";
|
|
5
5
|
import { PaymentRequirementsExtra } from "./facilitator.js";
|
|
6
6
|
import { generateMatcher } from "./common.js";
|
|
7
|
+
import { createMemoInstruction, generateMemoNonce } from "./memo.js";
|
|
7
8
|
import { logger } from "./logger.js";
|
|
8
9
|
function generateGetAssociatedTokenAddressSyncRest(tokenConfig) {
|
|
9
10
|
const { allowOwnerOffCurve, programId, associatedTokenProgramId } = tokenConfig;
|
|
@@ -56,6 +57,7 @@ async function extractMetadata(args) {
|
|
|
56
57
|
const tokenProgramId = extra.tokenProgram
|
|
57
58
|
? new PublicKey(extra.tokenProgram)
|
|
58
59
|
: (options?.token?.programId ?? TOKEN_PROGRAM_ID);
|
|
60
|
+
const memo = extra.memo;
|
|
59
61
|
return {
|
|
60
62
|
recentBlockhash,
|
|
61
63
|
decimals,
|
|
@@ -64,6 +66,7 @@ async function extractMetadata(args) {
|
|
|
64
66
|
payerKey,
|
|
65
67
|
paymentMode,
|
|
66
68
|
tokenProgramId,
|
|
69
|
+
memo,
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
/**
|
|
@@ -99,7 +102,7 @@ export function createPaymentHandler(wallet, mint, connection, options) {
|
|
|
99
102
|
const compatibleRequirements = accepts.filter(isMatchingRequirement);
|
|
100
103
|
const res = compatibleRequirements.map((requirements) => {
|
|
101
104
|
const exec = async () => {
|
|
102
|
-
const { recentBlockhash, decimals, payTo, amount, payerKey, paymentMode, tokenProgramId, } = await extractMetadata({
|
|
105
|
+
const { recentBlockhash, decimals, payTo, amount, payerKey, paymentMode, tokenProgramId, memo, } = await extractMetadata({
|
|
103
106
|
connection,
|
|
104
107
|
mint,
|
|
105
108
|
requirements,
|
|
@@ -122,7 +125,7 @@ export function createPaymentHandler(wallet, mint, connection, options) {
|
|
|
122
125
|
switch (paymentMode) {
|
|
123
126
|
case PaymentMode.ToSpec: {
|
|
124
127
|
const receiverAccount = getAssociatedTokenAddressSync(mint, payTo, ...getAssociatedTokenAddressSyncRest);
|
|
125
|
-
instructions.push(createTransferCheckedInstruction(sourceAccount, mint, receiverAccount, wallet.publicKey, amount, decimals, undefined, tokenProgramId));
|
|
128
|
+
instructions.push(createTransferCheckedInstruction(sourceAccount, mint, receiverAccount, wallet.publicKey, amount, decimals, undefined, tokenProgramId), createMemoInstruction(memo ?? generateMemoNonce()));
|
|
126
129
|
let tx;
|
|
127
130
|
if (wallet.buildTransaction) {
|
|
128
131
|
tx = await wallet.buildTransaction(instructions, recentBlockhash);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facilitator.d.ts","sourceRoot":"","sources":["../../../src/exact/facilitator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAExB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAML,KAAK,kBAAkB,EACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,SAAS,EAIV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAYL,KAAK,GAAG,EACR,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;AAOrB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGrD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAKlC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAAC;IACrC,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;IAChD,YAAY,EAAE,uBAAuB,CAAC;IACtC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,OAAO,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,CAAC,QAAQ,IAAI,YAAY,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,iBAAiB,CAAC,QAAQ,IAAI,CACxC,IAAI,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IACpD,WAAW,CAAC,EAAE,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;CACrD;AAED,eAAO,MAAM,gCAAgC;;MAE3C,CAAC;AAEH,MAAM,MAAM,gCAAgC,GAC1C,OAAO,gCAAgC,CAAC,KAAK,CAAC;AAEhD,eAAO,MAAM,wBAAwB
|
|
1
|
+
{"version":3,"file":"facilitator.d.ts","sourceRoot":"","sources":["../../../src/exact/facilitator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAExB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAML,KAAK,kBAAkB,EACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,SAAS,EAIV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAYL,KAAK,GAAG,EACR,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;AAOrB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGrD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAKlC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAAC;IACrC,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;IAChD,YAAY,EAAE,uBAAuB,CAAC;IACtC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,OAAO,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,CAAC,QAAQ,IAAI,YAAY,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,iBAAiB,CAAC,QAAQ,IAAI,CACxC,IAAI,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IACpD,WAAW,CAAC,EAAE,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;CACrD;AAED,eAAO,MAAM,gCAAgC;;MAE3C,CAAC;AAEH,MAAM,MAAM,gCAAgC,GAC1C,OAAO,gCAAgC,CAAC,KAAK,CAAC;AAEhD,eAAO,MAAM,wBAAwB;;;;;;;;;MAOnC,CAAC;AAEH,UAAU,kBAAkB;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE;QACT,wBAAwB,CAAC,EAAE,OAAO,CAAC;QACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;IACF,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACrC;AASD,eAAO,MAAM,yBAAyB;;;;;MAEpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,OAAO,yBAAyB,CAAC,KAAK,CAAC;AAE/E,eAAO,MAAM,+BAA+B;;;;MAM1C,CAAC;AACH,MAAM,MAAM,+BAA+B,GACzC,OAAO,+BAA+B,CAAC,KAAK,CAAC;AAE/C,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,gBAAgB,UAY3D;AAiDD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS,MAAM,GAAG,kBAAkB,EACpC,KAAK,GAAG,CAAC,YAAY,CAAC,EACtB,iBAAiB,OAAO,EACxB,MAAM,SAAS,EACf,SAAS,kBAAkB,KAC1B,OAAO,CAAC,kBAAkB,CA0c5B,CAAC"}
|
|
@@ -18,6 +18,7 @@ export const PaymentRequirementsExtra = type({
|
|
|
18
18
|
feePayer: "string",
|
|
19
19
|
decimals: "number?",
|
|
20
20
|
recentBlockhash: "string?",
|
|
21
|
+
"memo?": "string",
|
|
21
22
|
"tokenProgram?": "string",
|
|
22
23
|
features: PaymentRequirementsExtraFeatures.optional(),
|
|
23
24
|
});
|
|
@@ -276,6 +277,10 @@ export const createFacilitatorHandler = async (network, rpc, feePayerKeypair, mi
|
|
|
276
277
|
const recentBlockhash = (await rpc.getLatestBlockhash().send()).value
|
|
277
278
|
.blockhash;
|
|
278
279
|
return req.filter(isMatchingRequirement).map((x) => {
|
|
280
|
+
const incomingExtra = PaymentRequirementsExtra(x.extra);
|
|
281
|
+
const memo = !isValidationError(incomingExtra) && incomingExtra.memo !== undefined
|
|
282
|
+
? { memo: incomingExtra.memo }
|
|
283
|
+
: {};
|
|
279
284
|
return {
|
|
280
285
|
...x,
|
|
281
286
|
asset: mint.toBase58(),
|
|
@@ -283,6 +288,7 @@ export const createFacilitatorHandler = async (network, rpc, feePayerKeypair, mi
|
|
|
283
288
|
feePayer: feePayerKeypair.publicKey.toString(),
|
|
284
289
|
decimals: mintInfo.data.decimals,
|
|
285
290
|
recentBlockhash,
|
|
291
|
+
...memo,
|
|
286
292
|
tokenProgram,
|
|
287
293
|
features,
|
|
288
294
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/exact/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/exact/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../../../src/exact/memo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGpE,wBAAgB,iBAAiB,IAAI,MAAM,CAM1C;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,sBAAsB,CAM1E"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
2
|
+
import { MEMO_PROGRAM_ADDRESS } from "@solana-program/memo";
|
|
3
|
+
export function generateMemoNonce() {
|
|
4
|
+
const bytes = new Uint8Array(16);
|
|
5
|
+
crypto.getRandomValues(bytes);
|
|
6
|
+
return Array.from(bytes)
|
|
7
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
8
|
+
.join("");
|
|
9
|
+
}
|
|
10
|
+
export function createMemoInstruction(memo) {
|
|
11
|
+
return new TransactionInstruction({
|
|
12
|
+
programId: new PublicKey(MEMO_PROGRAM_ADDRESS),
|
|
13
|
+
keys: [],
|
|
14
|
+
data: Buffer.from(memo, "utf-8"),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { x402PaymentRequirements } from "@faremeter/types/x402v2";
|
|
2
|
-
import { type Address
|
|
2
|
+
import { type Address } from "@solana/kit";
|
|
3
|
+
import type { CompilableTransactionMessage } from "../common.js";
|
|
3
4
|
export declare function isValidTransaction(transactionMessage: CompilableTransactionMessage, paymentRequirements: x402PaymentRequirements, facilitatorAddress: string, tokenProgram: Address, maxPriorityFee?: number): Promise<{
|
|
4
5
|
payer: string;
|
|
5
6
|
} | false>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/exact/verify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAUvE,OAAO,
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/exact/verify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAUvE,OAAO,EAAW,KAAK,OAAO,EAAoB,MAAM,aAAa,CAAC;AAGtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AA0H9D,wBAAsB,kBAAkB,CACtC,kBAAkB,EAAE,4BAA4B,EAChD,mBAAmB,EAAE,uBAAuB,EAC5C,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,OAAO,EACrB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAAC,CAkGpC"}
|
package/dist/src/exact/verify.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { isValidationError } from "@faremeter/types";
|
|
2
2
|
import { parseSetComputeUnitLimitInstruction, parseSetComputeUnitPriceInstruction, } from "@solana-program/compute-budget";
|
|
3
3
|
import { findAssociatedTokenPda, parseTransferCheckedInstruction, } from "@solana-program/token";
|
|
4
|
-
import { address
|
|
4
|
+
import { address } from "@solana/kit";
|
|
5
|
+
import { MEMO_PROGRAM_ADDRESS } from "@solana-program/memo";
|
|
5
6
|
import { PaymentRequirementsExtra } from "./facilitator.js";
|
|
6
7
|
import { logger } from "./logger.js";
|
|
7
8
|
const LIGHTHOUSE_PROGRAM_ADDRESS = address("L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95");
|
|
8
9
|
function isLighthouseInstruction(instruction) {
|
|
9
10
|
return instruction.programAddress === LIGHTHOUSE_PROGRAM_ADDRESS;
|
|
10
11
|
}
|
|
12
|
+
function isMemoInstruction(instruction) {
|
|
13
|
+
return instruction.programAddress === MEMO_PROGRAM_ADDRESS;
|
|
14
|
+
}
|
|
15
|
+
function isAllowedTrailingInstruction(instruction) {
|
|
16
|
+
return isLighthouseInstruction(instruction) || isMemoInstruction(instruction);
|
|
17
|
+
}
|
|
18
|
+
function getMemoData(instruction) {
|
|
19
|
+
if (!isMemoInstruction(instruction) || !instruction.data) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return new TextDecoder().decode(new Uint8Array(instruction.data));
|
|
23
|
+
}
|
|
11
24
|
function verifyComputeUnitLimitInstruction(instruction) {
|
|
12
25
|
if (!instruction.data) {
|
|
13
26
|
return { valid: false };
|
|
@@ -38,6 +51,10 @@ function verifyComputeUnitPriceInstruction(instruction) {
|
|
|
38
51
|
return { valid: false };
|
|
39
52
|
}
|
|
40
53
|
}
|
|
54
|
+
// The upstream spec caps the per-CU price (<=5 lamports/CU), but that still
|
|
55
|
+
// allows an attacker to inflate the CU limit to the Solana maximum and drain
|
|
56
|
+
// the facilitator's SOL. A total-fee cap closes that vector because the
|
|
57
|
+
// facilitator is the one paying the priority fee.
|
|
41
58
|
function calculatePriorityFee(units, microLamports) {
|
|
42
59
|
return (units * Number(microLamports)) / 1_000_000;
|
|
43
60
|
}
|
|
@@ -90,7 +107,7 @@ export async function isValidTransaction(transactionMessage, paymentRequirements
|
|
|
90
107
|
tokenProgram,
|
|
91
108
|
});
|
|
92
109
|
const instructions = transactionMessage.instructions;
|
|
93
|
-
if (instructions.length < 3 || instructions.length >
|
|
110
|
+
if (instructions.length < 3 || instructions.length > 6) {
|
|
94
111
|
return false;
|
|
95
112
|
}
|
|
96
113
|
const [ix0, ix1, ix2, ...rest] = instructions;
|
|
@@ -111,10 +128,37 @@ export async function isValidTransaction(transactionMessage, paymentRequirements
|
|
|
111
128
|
return false;
|
|
112
129
|
}
|
|
113
130
|
}
|
|
114
|
-
if (!rest.every(
|
|
115
|
-
logger.error("Dropping transaction with
|
|
131
|
+
if (!rest.every(isAllowedTrailingInstruction)) {
|
|
132
|
+
logger.error("Dropping transaction with unexpected trailing instructions");
|
|
116
133
|
return false;
|
|
117
134
|
}
|
|
135
|
+
const facilitator = address(facilitatorAddress);
|
|
136
|
+
for (const ix of instructions) {
|
|
137
|
+
if (!ix.accounts)
|
|
138
|
+
continue;
|
|
139
|
+
for (const account of ix.accounts) {
|
|
140
|
+
if (account.address === facilitator) {
|
|
141
|
+
logger.error("Dropping transaction where the facilitator appears in instruction accounts");
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const memoInstructions = rest.filter(isMemoInstruction);
|
|
147
|
+
if (memoInstructions.length !== 1) {
|
|
148
|
+
logger.error("Expected exactly one Memo instruction");
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (extra.memo !== undefined) {
|
|
152
|
+
const memoIx = memoInstructions[0];
|
|
153
|
+
if (!memoIx) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const memoData = getMemoData(memoIx);
|
|
157
|
+
if (memoData !== extra.memo) {
|
|
158
|
+
logger.error("Memo instruction data does not match extra.memo");
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
118
162
|
const payer = await verifyTransferInstruction(ix2, paymentRequirements, destination, facilitatorAddress, tokenProgram);
|
|
119
163
|
if (!payer)
|
|
120
164
|
return false;
|
|
@@ -3,6 +3,7 @@ import t from "tap";
|
|
|
3
3
|
import { isValidTransaction } from "./verify.js";
|
|
4
4
|
import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction, } from "@solana-program/compute-budget";
|
|
5
5
|
import { findAssociatedTokenPda, getTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS, } from "@solana-program/token";
|
|
6
|
+
import { MEMO_PROGRAM_ADDRESS } from "@solana-program/memo";
|
|
6
7
|
import { TOKEN_2022_PROGRAM_ADDRESS } from "../splToken.js";
|
|
7
8
|
import { address, appendTransactionMessageInstructions, createTransactionMessage, generateKeyPairSigner, pipe, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, } from "@solana/kit";
|
|
8
9
|
function createRequirements(overrides) {
|
|
@@ -85,24 +86,36 @@ function makeLighthouseIx(data) {
|
|
|
85
86
|
data: new Uint8Array(data ?? [0]),
|
|
86
87
|
};
|
|
87
88
|
}
|
|
89
|
+
function makeMemoIx(memo) {
|
|
90
|
+
return {
|
|
91
|
+
programAddress: MEMO_PROGRAM_ADDRESS,
|
|
92
|
+
data: new TextEncoder().encode(memo),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
88
95
|
await t.test("isValidTransaction", async (t) => {
|
|
89
|
-
await t.test("accepts valid
|
|
96
|
+
await t.test("accepts valid 4-instruction transaction", async (t) => {
|
|
90
97
|
const f = await createFixtures();
|
|
91
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
98
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, makeMemoIx("nonce")], f.facilitator);
|
|
92
99
|
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram);
|
|
93
100
|
t.ok(result);
|
|
94
101
|
t.equal(result && result.payer, f.sender.address);
|
|
95
102
|
t.end();
|
|
96
103
|
});
|
|
97
|
-
await t.test("accepts valid
|
|
104
|
+
await t.test("accepts valid 5-instruction transaction with one lighthouse ix", async (t) => {
|
|
98
105
|
const f = await createFixtures();
|
|
99
|
-
const txMsg = buildTxMessage([
|
|
106
|
+
const txMsg = buildTxMessage([
|
|
107
|
+
f.computeLimitIx,
|
|
108
|
+
f.computePriceIx,
|
|
109
|
+
f.transferIx,
|
|
110
|
+
makeLighthouseIx(),
|
|
111
|
+
makeMemoIx("nonce"),
|
|
112
|
+
], f.facilitator);
|
|
100
113
|
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram);
|
|
101
114
|
t.ok(result);
|
|
102
115
|
t.equal(result && result.payer, f.sender.address);
|
|
103
116
|
t.end();
|
|
104
117
|
});
|
|
105
|
-
await t.test("accepts valid
|
|
118
|
+
await t.test("accepts valid 6-instruction transaction with two lighthouse ixs", async (t) => {
|
|
106
119
|
const f = await createFixtures();
|
|
107
120
|
const txMsg = buildTxMessage([
|
|
108
121
|
f.computeLimitIx,
|
|
@@ -110,6 +123,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
110
123
|
f.transferIx,
|
|
111
124
|
makeLighthouseIx([1]),
|
|
112
125
|
makeLighthouseIx([2]),
|
|
126
|
+
makeMemoIx("nonce"),
|
|
113
127
|
], f.facilitator);
|
|
114
128
|
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram);
|
|
115
129
|
t.ok(result);
|
|
@@ -122,13 +136,19 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
122
136
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
123
137
|
t.end();
|
|
124
138
|
});
|
|
125
|
-
await t.test("rejects transaction with more than
|
|
139
|
+
await t.test("rejects transaction with more than 6 instructions", async (t) => {
|
|
126
140
|
const f = await createFixtures();
|
|
127
|
-
const extras = Array.from({ length:
|
|
141
|
+
const extras = Array.from({ length: 4 }, (_, i) => makeLighthouseIx([i]));
|
|
128
142
|
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, ...extras], f.facilitator);
|
|
129
143
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
130
144
|
t.end();
|
|
131
145
|
});
|
|
146
|
+
await t.test("rejects transaction without memo instruction", async (t) => {
|
|
147
|
+
const f = await createFixtures();
|
|
148
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
149
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
150
|
+
t.end();
|
|
151
|
+
});
|
|
132
152
|
await t.test("rejects transaction with wrong fee payer", async (t) => {
|
|
133
153
|
const f = await createFixtures();
|
|
134
154
|
const wrongPayer = await generateKeyPairSigner();
|
|
@@ -154,7 +174,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
154
174
|
});
|
|
155
175
|
await t.test("accepts transaction within priority fee limit", async (t) => {
|
|
156
176
|
const f = await createFixtures();
|
|
157
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
177
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, makeMemoIx("nonce")], f.facilitator);
|
|
158
178
|
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram, 100_000);
|
|
159
179
|
t.ok(result);
|
|
160
180
|
t.equal(result && result.payer, f.sender.address);
|
|
@@ -168,7 +188,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
168
188
|
const highPriceIx = getSetComputeUnitPriceInstruction({
|
|
169
189
|
microLamports: 10000000n,
|
|
170
190
|
});
|
|
171
|
-
const txMsg = buildTxMessage([highLimitIx, highPriceIx, f.transferIx], f.facilitator);
|
|
191
|
+
const txMsg = buildTxMessage([highLimitIx, highPriceIx, f.transferIx, makeMemoIx("nonce")], f.facilitator);
|
|
172
192
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram, 100), false);
|
|
173
193
|
t.end();
|
|
174
194
|
});
|
|
@@ -180,7 +200,13 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
180
200
|
const highPriceIx = getSetComputeUnitPriceInstruction({
|
|
181
201
|
microLamports: 10000000n,
|
|
182
202
|
});
|
|
183
|
-
const txMsg = buildTxMessage([
|
|
203
|
+
const txMsg = buildTxMessage([
|
|
204
|
+
highLimitIx,
|
|
205
|
+
highPriceIx,
|
|
206
|
+
f.transferIx,
|
|
207
|
+
makeLighthouseIx(),
|
|
208
|
+
makeMemoIx("nonce"),
|
|
209
|
+
], f.facilitator);
|
|
184
210
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram, 100), false);
|
|
185
211
|
t.end();
|
|
186
212
|
});
|
|
@@ -222,7 +248,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
222
248
|
amount: f.amount + 1n,
|
|
223
249
|
decimals: f.decimals,
|
|
224
250
|
});
|
|
225
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongAmountIx], f.facilitator);
|
|
251
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongAmountIx, makeMemoIx("nonce")], f.facilitator);
|
|
226
252
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
227
253
|
t.end();
|
|
228
254
|
});
|
|
@@ -247,7 +273,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
247
273
|
amount: f.amount,
|
|
248
274
|
decimals: f.decimals,
|
|
249
275
|
});
|
|
250
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongMintIx], f.facilitator);
|
|
276
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongMintIx, makeMemoIx("nonce")], f.facilitator);
|
|
251
277
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
252
278
|
t.end();
|
|
253
279
|
});
|
|
@@ -267,7 +293,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
267
293
|
amount: f.amount,
|
|
268
294
|
decimals: f.decimals,
|
|
269
295
|
});
|
|
270
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongDestIx], f.facilitator);
|
|
296
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongDestIx, makeMemoIx("nonce")], f.facilitator);
|
|
271
297
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
272
298
|
t.end();
|
|
273
299
|
});
|
|
@@ -281,7 +307,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
281
307
|
amount: f.amount,
|
|
282
308
|
decimals: f.decimals,
|
|
283
309
|
});
|
|
284
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, badIx], f.facilitator);
|
|
310
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, badIx, makeMemoIx("nonce")], f.facilitator);
|
|
285
311
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
286
312
|
t.end();
|
|
287
313
|
});
|
|
@@ -295,7 +321,7 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
295
321
|
amount: f.amount,
|
|
296
322
|
decimals: f.decimals,
|
|
297
323
|
});
|
|
298
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, badIx], f.facilitator);
|
|
324
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, badIx, makeMemoIx("nonce")], f.facilitator);
|
|
299
325
|
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
300
326
|
t.end();
|
|
301
327
|
});
|
|
@@ -303,11 +329,124 @@ await t.test("isValidTransaction", async (t) => {
|
|
|
303
329
|
const f = await createFixtures({
|
|
304
330
|
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
|
|
305
331
|
});
|
|
306
|
-
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
332
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, makeMemoIx("nonce")], f.facilitator);
|
|
333
|
+
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram);
|
|
334
|
+
t.ok(result);
|
|
335
|
+
t.equal(result && result.payer, f.sender.address);
|
|
336
|
+
t.end();
|
|
337
|
+
});
|
|
338
|
+
await t.test("accepts transaction with memo instruction", async (t) => {
|
|
339
|
+
const f = await createFixtures();
|
|
340
|
+
const txMsg = buildTxMessage([
|
|
341
|
+
f.computeLimitIx,
|
|
342
|
+
f.computePriceIx,
|
|
343
|
+
f.transferIx,
|
|
344
|
+
makeMemoIx("some-random-nonce"),
|
|
345
|
+
], f.facilitator);
|
|
346
|
+
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram);
|
|
347
|
+
t.ok(result);
|
|
348
|
+
t.equal(result && result.payer, f.sender.address);
|
|
349
|
+
t.end();
|
|
350
|
+
});
|
|
351
|
+
await t.test("accepts transaction with lighthouse and memo instructions", async (t) => {
|
|
352
|
+
const f = await createFixtures();
|
|
353
|
+
const txMsg = buildTxMessage([
|
|
354
|
+
f.computeLimitIx,
|
|
355
|
+
f.computePriceIx,
|
|
356
|
+
f.transferIx,
|
|
357
|
+
makeLighthouseIx(),
|
|
358
|
+
makeMemoIx("nonce-123"),
|
|
359
|
+
], f.facilitator);
|
|
307
360
|
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram);
|
|
308
361
|
t.ok(result);
|
|
309
362
|
t.equal(result && result.payer, f.sender.address);
|
|
310
363
|
t.end();
|
|
311
364
|
});
|
|
365
|
+
await t.test("validates memo matches extra.memo when set", async (t) => {
|
|
366
|
+
const f = await createFixtures();
|
|
367
|
+
const requirements = {
|
|
368
|
+
...f.requirements,
|
|
369
|
+
extra: { ...f.requirements.extra, memo: "invoice-456" },
|
|
370
|
+
};
|
|
371
|
+
const txMsg = buildTxMessage([
|
|
372
|
+
f.computeLimitIx,
|
|
373
|
+
f.computePriceIx,
|
|
374
|
+
f.transferIx,
|
|
375
|
+
makeMemoIx("invoice-456"),
|
|
376
|
+
], f.facilitator);
|
|
377
|
+
const result = await isValidTransaction(txMsg, requirements, f.facilitator.address, f.tokenProgram);
|
|
378
|
+
t.ok(result);
|
|
379
|
+
t.equal(result && result.payer, f.sender.address);
|
|
380
|
+
t.end();
|
|
381
|
+
});
|
|
382
|
+
await t.test("rejects memo mismatch when extra.memo is set", async (t) => {
|
|
383
|
+
const f = await createFixtures();
|
|
384
|
+
const requirements = {
|
|
385
|
+
...f.requirements,
|
|
386
|
+
extra: { ...f.requirements.extra, memo: "invoice-456" },
|
|
387
|
+
};
|
|
388
|
+
const txMsg = buildTxMessage([
|
|
389
|
+
f.computeLimitIx,
|
|
390
|
+
f.computePriceIx,
|
|
391
|
+
f.transferIx,
|
|
392
|
+
makeMemoIx("wrong-memo"),
|
|
393
|
+
], f.facilitator);
|
|
394
|
+
t.equal(await isValidTransaction(txMsg, requirements, f.facilitator.address, f.tokenProgram), false);
|
|
395
|
+
t.end();
|
|
396
|
+
});
|
|
397
|
+
await t.test("rejects transaction where facilitator appears in trailing instruction accounts", async (t) => {
|
|
398
|
+
const f = await createFixtures();
|
|
399
|
+
const badMemoIx = {
|
|
400
|
+
programAddress: MEMO_PROGRAM_ADDRESS,
|
|
401
|
+
data: new TextEncoder().encode("nonce"),
|
|
402
|
+
accounts: [
|
|
403
|
+
{
|
|
404
|
+
address: f.facilitator.address,
|
|
405
|
+
role: 0,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
};
|
|
409
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, badMemoIx], f.facilitator);
|
|
410
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
411
|
+
t.end();
|
|
412
|
+
});
|
|
413
|
+
await t.test("rejects transaction with multiple memo instructions", async (t) => {
|
|
414
|
+
const f = await createFixtures();
|
|
415
|
+
const txMsg = buildTxMessage([
|
|
416
|
+
f.computeLimitIx,
|
|
417
|
+
f.computePriceIx,
|
|
418
|
+
f.transferIx,
|
|
419
|
+
makeMemoIx("nonce-1"),
|
|
420
|
+
makeMemoIx("nonce-2"),
|
|
421
|
+
], f.facilitator);
|
|
422
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, f.tokenProgram), false);
|
|
423
|
+
t.end();
|
|
424
|
+
});
|
|
425
|
+
await t.test("rejects missing memo when extra.memo is set", async (t) => {
|
|
426
|
+
const f = await createFixtures();
|
|
427
|
+
const requirements = {
|
|
428
|
+
...f.requirements,
|
|
429
|
+
extra: { ...f.requirements.extra, memo: "invoice-456" },
|
|
430
|
+
};
|
|
431
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
432
|
+
t.equal(await isValidTransaction(txMsg, requirements, f.facilitator.address, f.tokenProgram), false);
|
|
433
|
+
t.end();
|
|
434
|
+
});
|
|
435
|
+
await t.test("rejects multiple memo instructions when extra.memo is set", async (t) => {
|
|
436
|
+
const f = await createFixtures();
|
|
437
|
+
const requirements = {
|
|
438
|
+
...f.requirements,
|
|
439
|
+
extra: { ...f.requirements.extra, memo: "invoice-456" },
|
|
440
|
+
};
|
|
441
|
+
const txMsg = buildTxMessage([
|
|
442
|
+
f.computeLimitIx,
|
|
443
|
+
f.computePriceIx,
|
|
444
|
+
f.transferIx,
|
|
445
|
+
makeMemoIx("invoice-456"),
|
|
446
|
+
makeMemoIx("invoice-456"),
|
|
447
|
+
], f.facilitator);
|
|
448
|
+
t.equal(await isValidTransaction(txMsg, requirements, f.facilitator.address, f.tokenProgram), false);
|
|
449
|
+
t.end();
|
|
450
|
+
});
|
|
312
451
|
t.end();
|
|
313
452
|
});
|
package/dist/src/index.d.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* @description SPL token transfer payment handlers for Solana
|
|
11
11
|
*/
|
|
12
12
|
export * as exact from "./exact/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* @title Solana Charge Payment Scheme
|
|
15
|
+
* @sidebarTitle Payment Solana / Charge
|
|
16
|
+
* @description MPP charge-based payment handlers for Solana
|
|
17
|
+
*/
|
|
18
|
+
export * as charge from "./charge/index.js";
|
|
13
19
|
/**
|
|
14
20
|
* @title SPL Token Utilities
|
|
15
21
|
* @sidebarTitle Payment Solana / SPL Token
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH;;;;GAIG;AACH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC;;;;GAIG;AACH,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH;;;;GAIG;AACH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC;;;;GAIG;AACH,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* @description SPL token transfer payment handlers for Solana
|
|
11
11
|
*/
|
|
12
12
|
export * as exact from "./exact/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* @title Solana Charge Payment Scheme
|
|
15
|
+
* @sidebarTitle Payment Solana / Charge
|
|
16
|
+
* @description MPP charge-based payment handlers for Solana
|
|
17
|
+
*/
|
|
18
|
+
export * as charge from "./charge/index.js";
|
|
13
19
|
/**
|
|
14
20
|
* @title SPL Token Utilities
|
|
15
21
|
* @sidebarTitle Payment Solana / SPL Token
|