@faremeter/payment-solana 0.16.0 → 0.17.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/dist/src/exact/cache.d.ts +12 -0
- package/dist/src/exact/cache.d.ts.map +1 -0
- package/dist/src/exact/cache.js +31 -0
- package/dist/src/exact/client.d.ts +16 -1
- package/dist/src/exact/client.d.ts.map +1 -1
- package/dist/src/exact/client.js +34 -8
- package/dist/src/exact/common.d.ts +2 -1
- package/dist/src/exact/common.d.ts.map +1 -1
- package/dist/src/exact/common.js +3 -4
- package/dist/src/exact/common.test.js +11 -3
- package/dist/src/exact/facilitator.d.ts +20 -3
- package/dist/src/exact/facilitator.d.ts.map +1 -1
- package/dist/src/exact/facilitator.js +92 -29
- package/dist/src/exact/facilitator.test.js +35 -0
- package/dist/src/exact/verify.d.ts +4 -3
- package/dist/src/exact/verify.d.ts.map +1 -1
- package/dist/src/exact/verify.js +34 -64
- package/dist/src/exact/verify.test.d.ts +3 -0
- package/dist/src/exact/verify.test.d.ts.map +1 -0
- package/dist/src/exact/verify.test.js +301 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +16 -0
- package/dist/src/splToken.d.ts +26 -0
- package/dist/src/splToken.d.ts.map +1 -1
- package/dist/src/splToken.js +20 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
package/dist/src/exact/verify.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { isValidationError } from "@faremeter/types";
|
|
2
2
|
import { parseSetComputeUnitLimitInstruction, parseSetComputeUnitPriceInstruction, } from "@solana-program/compute-budget";
|
|
3
|
-
import { findAssociatedTokenPda,
|
|
3
|
+
import { findAssociatedTokenPda, parseTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS, } from "@solana-program/token";
|
|
4
4
|
import { address, } from "@solana/kit";
|
|
5
5
|
import { PaymentRequirementsExtra } from "./facilitator.js";
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
|
+
const LIGHTHOUSE_PROGRAM_ADDRESS = address("L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95");
|
|
8
|
+
function isLighthouseInstruction(instruction) {
|
|
9
|
+
return instruction.programAddress === LIGHTHOUSE_PROGRAM_ADDRESS;
|
|
10
|
+
}
|
|
7
11
|
function verifyComputeUnitLimitInstruction(instruction) {
|
|
8
12
|
if (!instruction.data) {
|
|
9
13
|
return { valid: false };
|
|
@@ -65,29 +69,12 @@ async function verifyTransferInstruction(instruction, paymentRequirements, desti
|
|
|
65
69
|
logger.error("Dropping transfer where the source is the facilitator");
|
|
66
70
|
return false;
|
|
67
71
|
}
|
|
68
|
-
|
|
72
|
+
if (transfer.data.amount === BigInt(paymentRequirements.amount) &&
|
|
69
73
|
transfer.accounts.mint.address === paymentRequirements.asset &&
|
|
70
|
-
transfer.accounts.destination.address === destination)
|
|
71
|
-
|
|
72
|
-
function verifyCreateATAInstruction(instruction, facilitatorAddress) {
|
|
73
|
-
if (!instruction.data || !instruction.accounts) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const createInstruction = parseCreateAssociatedTokenInstruction({
|
|
78
|
-
accounts: instruction.accounts,
|
|
79
|
-
programAddress: instruction.programAddress,
|
|
80
|
-
data: new Uint8Array(instruction.data),
|
|
81
|
-
});
|
|
82
|
-
if (createInstruction.accounts.payer.address === facilitatorAddress) {
|
|
83
|
-
logger.error("Dropping transaction where the facilitator pays for a token account creation");
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return false;
|
|
74
|
+
transfer.accounts.destination.address === destination) {
|
|
75
|
+
return transfer.accounts.authority.address;
|
|
90
76
|
}
|
|
77
|
+
return false;
|
|
91
78
|
}
|
|
92
79
|
export async function isValidTransaction(transactionMessage, paymentRequirements, facilitatorAddress, maxPriorityFee) {
|
|
93
80
|
const extra = PaymentRequirementsExtra(paymentRequirements.extra);
|
|
@@ -103,50 +90,33 @@ export async function isValidTransaction(transactionMessage, paymentRequirements
|
|
|
103
90
|
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
104
91
|
});
|
|
105
92
|
const instructions = transactionMessage.instructions;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Make typescript happy...
|
|
109
|
-
const [ix0, ix1, ix2] = instructions;
|
|
110
|
-
if (!ix0 || !ix1 || !ix2) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
const limitResult = verifyComputeUnitLimitInstruction(ix0);
|
|
114
|
-
const priceResult = verifyComputeUnitPriceInstruction(ix1);
|
|
115
|
-
if (!limitResult.valid || !priceResult.valid) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
if (maxPriorityFee !== undefined &&
|
|
119
|
-
limitResult.units !== undefined &&
|
|
120
|
-
priceResult.microLamports !== undefined) {
|
|
121
|
-
const priorityFee = calculatePriorityFee(limitResult.units, priceResult.microLamports);
|
|
122
|
-
if (priorityFee > maxPriorityFee) {
|
|
123
|
-
logger.error(`Priority fee ${priorityFee} exceeds maximum ${maxPriorityFee}`);
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return await verifyTransferInstruction(ix2, paymentRequirements, destination, facilitatorBase58);
|
|
93
|
+
if (instructions.length < 3 || instructions.length > 5) {
|
|
94
|
+
return false;
|
|
128
95
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
96
|
+
const [ix0, ix1, ix2, ...rest] = instructions;
|
|
97
|
+
if (!ix0 || !ix1 || !ix2) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const limitResult = verifyComputeUnitLimitInstruction(ix0);
|
|
101
|
+
const priceResult = verifyComputeUnitPriceInstruction(ix1);
|
|
102
|
+
if (!limitResult.valid || !priceResult.valid) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (maxPriorityFee !== undefined &&
|
|
106
|
+
limitResult.units !== undefined &&
|
|
107
|
+
priceResult.microLamports !== undefined) {
|
|
108
|
+
const priorityFee = calculatePriorityFee(limitResult.units, priceResult.microLamports);
|
|
109
|
+
if (priorityFee > maxPriorityFee) {
|
|
110
|
+
logger.error(`Priority fee ${priorityFee} exceeds maximum ${maxPriorityFee}`);
|
|
137
111
|
return false;
|
|
138
112
|
}
|
|
139
|
-
if (maxPriorityFee !== undefined &&
|
|
140
|
-
limitResult.units !== undefined &&
|
|
141
|
-
priceResult.microLamports !== undefined) {
|
|
142
|
-
const priorityFee = calculatePriorityFee(limitResult.units, priceResult.microLamports);
|
|
143
|
-
if (priorityFee > maxPriorityFee) {
|
|
144
|
-
logger.error(`Priority fee ${priorityFee} exceeds maximum ${maxPriorityFee}`);
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return (verifyCreateATAInstruction(ix2, facilitatorBase58) &&
|
|
149
|
-
(await verifyTransferInstruction(ix3, paymentRequirements, destination, facilitatorBase58)));
|
|
150
113
|
}
|
|
151
|
-
|
|
114
|
+
if (!rest.every(isLighthouseInstruction)) {
|
|
115
|
+
logger.error("Dropping transaction with non-Lighthouse trailing instructions");
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const payer = await verifyTransferInstruction(ix2, paymentRequirements, destination, facilitatorAddress);
|
|
119
|
+
if (!payer)
|
|
120
|
+
return false;
|
|
121
|
+
return { payer };
|
|
152
122
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.test.d.ts","sourceRoot":"","sources":["../../../src/exact/verify.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env pnpm tsx
|
|
2
|
+
import t from "tap";
|
|
3
|
+
import { isValidTransaction } from "./verify.js";
|
|
4
|
+
import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction, } from "@solana-program/compute-budget";
|
|
5
|
+
import { findAssociatedTokenPda, getTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS, } from "@solana-program/token";
|
|
6
|
+
import { address, appendTransactionMessageInstructions, createTransactionMessage, generateKeyPairSigner, pipe, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, } from "@solana/kit";
|
|
7
|
+
function createRequirements(overrides) {
|
|
8
|
+
return {
|
|
9
|
+
scheme: "exact",
|
|
10
|
+
network: "solana-devnet",
|
|
11
|
+
maxTimeoutSeconds: 30,
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const LIGHTHOUSE_PROGRAM_ADDRESS = address("L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95");
|
|
16
|
+
const FAKE_BLOCKHASH = "EETubP46DHLkT9hAFKy4x2BoFUqUFvKjiiNVY3CaYRi3";
|
|
17
|
+
function buildTxMessage(instructions, feePayer) {
|
|
18
|
+
return pipe(createTransactionMessage({ version: 0 }), (msg) => setTransactionMessageFeePayer(feePayer.address, msg), (msg) => setTransactionMessageLifetimeUsingBlockhash({ blockhash: FAKE_BLOCKHASH, lastValidBlockHeight: 1000n }, msg), (msg) => appendTransactionMessageInstructions(instructions, msg));
|
|
19
|
+
}
|
|
20
|
+
async function createFixtures() {
|
|
21
|
+
const facilitator = await generateKeyPairSigner();
|
|
22
|
+
const sender = await generateKeyPairSigner();
|
|
23
|
+
const receiver = await generateKeyPairSigner();
|
|
24
|
+
const mint = await generateKeyPairSigner();
|
|
25
|
+
const [senderATA] = await findAssociatedTokenPda({
|
|
26
|
+
mint: mint.address,
|
|
27
|
+
owner: sender.address,
|
|
28
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
29
|
+
});
|
|
30
|
+
const [receiverATA] = await findAssociatedTokenPda({
|
|
31
|
+
mint: mint.address,
|
|
32
|
+
owner: receiver.address,
|
|
33
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
34
|
+
});
|
|
35
|
+
const [facilitatorATA] = await findAssociatedTokenPda({
|
|
36
|
+
mint: mint.address,
|
|
37
|
+
owner: facilitator.address,
|
|
38
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
39
|
+
});
|
|
40
|
+
const amount = 1000000n;
|
|
41
|
+
const decimals = 6;
|
|
42
|
+
const requirements = createRequirements({
|
|
43
|
+
amount: amount.toString(),
|
|
44
|
+
payTo: receiver.address.toString(),
|
|
45
|
+
asset: mint.address.toString(),
|
|
46
|
+
extra: {
|
|
47
|
+
feePayer: facilitator.address.toString(),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const computeLimitIx = getSetComputeUnitLimitInstruction({
|
|
51
|
+
units: 50_000,
|
|
52
|
+
});
|
|
53
|
+
const computePriceIx = getSetComputeUnitPriceInstruction({
|
|
54
|
+
microLamports: 1n,
|
|
55
|
+
});
|
|
56
|
+
const transferIx = getTransferCheckedInstruction({
|
|
57
|
+
source: senderATA,
|
|
58
|
+
mint: mint.address,
|
|
59
|
+
destination: receiverATA,
|
|
60
|
+
authority: sender.address,
|
|
61
|
+
amount,
|
|
62
|
+
decimals,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
facilitator,
|
|
66
|
+
sender,
|
|
67
|
+
receiver,
|
|
68
|
+
mint,
|
|
69
|
+
senderATA,
|
|
70
|
+
receiverATA,
|
|
71
|
+
facilitatorATA,
|
|
72
|
+
amount,
|
|
73
|
+
decimals,
|
|
74
|
+
requirements,
|
|
75
|
+
computeLimitIx,
|
|
76
|
+
computePriceIx,
|
|
77
|
+
transferIx,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function makeLighthouseIx(data) {
|
|
81
|
+
return {
|
|
82
|
+
programAddress: LIGHTHOUSE_PROGRAM_ADDRESS,
|
|
83
|
+
data: new Uint8Array(data ?? [0]),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
await t.test("isValidTransaction", async (t) => {
|
|
87
|
+
await t.test("accepts valid 3-instruction transaction", async (t) => {
|
|
88
|
+
const f = await createFixtures();
|
|
89
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
90
|
+
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address);
|
|
91
|
+
t.ok(result);
|
|
92
|
+
t.equal(result && result.payer, f.sender.address);
|
|
93
|
+
t.end();
|
|
94
|
+
});
|
|
95
|
+
await t.test("accepts valid 4-instruction transaction with one lighthouse ix", async (t) => {
|
|
96
|
+
const f = await createFixtures();
|
|
97
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, makeLighthouseIx()], f.facilitator);
|
|
98
|
+
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address);
|
|
99
|
+
t.ok(result);
|
|
100
|
+
t.equal(result && result.payer, f.sender.address);
|
|
101
|
+
t.end();
|
|
102
|
+
});
|
|
103
|
+
await t.test("accepts valid 5-instruction transaction with two lighthouse ixs", async (t) => {
|
|
104
|
+
const f = await createFixtures();
|
|
105
|
+
const txMsg = buildTxMessage([
|
|
106
|
+
f.computeLimitIx,
|
|
107
|
+
f.computePriceIx,
|
|
108
|
+
f.transferIx,
|
|
109
|
+
makeLighthouseIx([1]),
|
|
110
|
+
makeLighthouseIx([2]),
|
|
111
|
+
], f.facilitator);
|
|
112
|
+
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address);
|
|
113
|
+
t.ok(result);
|
|
114
|
+
t.equal(result && result.payer, f.sender.address);
|
|
115
|
+
t.end();
|
|
116
|
+
});
|
|
117
|
+
await t.test("rejects transaction with fewer than 3 instructions", async (t) => {
|
|
118
|
+
const f = await createFixtures();
|
|
119
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx], f.facilitator);
|
|
120
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
121
|
+
t.end();
|
|
122
|
+
});
|
|
123
|
+
await t.test("rejects transaction with more than 5 instructions", async (t) => {
|
|
124
|
+
const f = await createFixtures();
|
|
125
|
+
const extras = Array.from({ length: 3 }, (_, i) => makeLighthouseIx([i]));
|
|
126
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, ...extras], f.facilitator);
|
|
127
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
128
|
+
t.end();
|
|
129
|
+
});
|
|
130
|
+
await t.test("rejects transaction with wrong fee payer", async (t) => {
|
|
131
|
+
const f = await createFixtures();
|
|
132
|
+
const wrongPayer = await generateKeyPairSigner();
|
|
133
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], wrongPayer);
|
|
134
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
135
|
+
t.end();
|
|
136
|
+
});
|
|
137
|
+
await t.test("throws when extra is missing feePayer", async (t) => {
|
|
138
|
+
const f = await createFixtures();
|
|
139
|
+
const badRequirements = {
|
|
140
|
+
...f.requirements,
|
|
141
|
+
extra: {},
|
|
142
|
+
};
|
|
143
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
144
|
+
await t.rejects(isValidTransaction(txMsg, badRequirements, f.facilitator.address));
|
|
145
|
+
t.end();
|
|
146
|
+
});
|
|
147
|
+
await t.test("rejects transaction with swapped compute budget instructions", async (t) => {
|
|
148
|
+
const f = await createFixtures();
|
|
149
|
+
const txMsg = buildTxMessage([f.computePriceIx, f.computeLimitIx, f.transferIx], f.facilitator);
|
|
150
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
151
|
+
t.end();
|
|
152
|
+
});
|
|
153
|
+
await t.test("accepts transaction within priority fee limit", async (t) => {
|
|
154
|
+
const f = await createFixtures();
|
|
155
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx], f.facilitator);
|
|
156
|
+
const result = await isValidTransaction(txMsg, f.requirements, f.facilitator.address, 100_000);
|
|
157
|
+
t.ok(result);
|
|
158
|
+
t.equal(result && result.payer, f.sender.address);
|
|
159
|
+
t.end();
|
|
160
|
+
});
|
|
161
|
+
await t.test("rejects transaction exceeding priority fee limit", async (t) => {
|
|
162
|
+
const f = await createFixtures();
|
|
163
|
+
const highLimitIx = getSetComputeUnitLimitInstruction({
|
|
164
|
+
units: 200_000,
|
|
165
|
+
});
|
|
166
|
+
const highPriceIx = getSetComputeUnitPriceInstruction({
|
|
167
|
+
microLamports: 10000000n,
|
|
168
|
+
});
|
|
169
|
+
const txMsg = buildTxMessage([highLimitIx, highPriceIx, f.transferIx], f.facilitator);
|
|
170
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, 100), false);
|
|
171
|
+
t.end();
|
|
172
|
+
});
|
|
173
|
+
await t.test("rejects transaction exceeding priority fee limit with lighthouse ixs", async (t) => {
|
|
174
|
+
const f = await createFixtures();
|
|
175
|
+
const highLimitIx = getSetComputeUnitLimitInstruction({
|
|
176
|
+
units: 200_000,
|
|
177
|
+
});
|
|
178
|
+
const highPriceIx = getSetComputeUnitPriceInstruction({
|
|
179
|
+
microLamports: 10000000n,
|
|
180
|
+
});
|
|
181
|
+
const txMsg = buildTxMessage([highLimitIx, highPriceIx, f.transferIx, makeLighthouseIx()], f.facilitator);
|
|
182
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address, 100), false);
|
|
183
|
+
t.end();
|
|
184
|
+
});
|
|
185
|
+
await t.test("rejects trailing non-lighthouse instruction", async (t) => {
|
|
186
|
+
const f = await createFixtures();
|
|
187
|
+
const randomSigner = await generateKeyPairSigner();
|
|
188
|
+
const fakeIx = {
|
|
189
|
+
programAddress: randomSigner.address,
|
|
190
|
+
data: new Uint8Array([0]),
|
|
191
|
+
};
|
|
192
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, f.transferIx, fakeIx], f.facilitator);
|
|
193
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
194
|
+
t.end();
|
|
195
|
+
});
|
|
196
|
+
await t.test("rejects mixed lighthouse and non-lighthouse trailing instructions", async (t) => {
|
|
197
|
+
const f = await createFixtures();
|
|
198
|
+
const randomSigner = await generateKeyPairSigner();
|
|
199
|
+
const fakeIx = {
|
|
200
|
+
programAddress: randomSigner.address,
|
|
201
|
+
data: new Uint8Array([0]),
|
|
202
|
+
};
|
|
203
|
+
const txMsg = buildTxMessage([
|
|
204
|
+
f.computeLimitIx,
|
|
205
|
+
f.computePriceIx,
|
|
206
|
+
f.transferIx,
|
|
207
|
+
makeLighthouseIx(),
|
|
208
|
+
fakeIx,
|
|
209
|
+
], f.facilitator);
|
|
210
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
211
|
+
t.end();
|
|
212
|
+
});
|
|
213
|
+
await t.test("rejects transaction with wrong transfer amount", async (t) => {
|
|
214
|
+
const f = await createFixtures();
|
|
215
|
+
const wrongAmountIx = getTransferCheckedInstruction({
|
|
216
|
+
source: f.senderATA,
|
|
217
|
+
mint: f.mint.address,
|
|
218
|
+
destination: f.receiverATA,
|
|
219
|
+
authority: f.sender.address,
|
|
220
|
+
amount: f.amount + 1n,
|
|
221
|
+
decimals: f.decimals,
|
|
222
|
+
});
|
|
223
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongAmountIx], f.facilitator);
|
|
224
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
225
|
+
t.end();
|
|
226
|
+
});
|
|
227
|
+
await t.test("rejects transaction with wrong mint", async (t) => {
|
|
228
|
+
const f = await createFixtures();
|
|
229
|
+
const wrongMint = await generateKeyPairSigner();
|
|
230
|
+
const [wrongMintSenderATA] = await findAssociatedTokenPda({
|
|
231
|
+
mint: wrongMint.address,
|
|
232
|
+
owner: f.sender.address,
|
|
233
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
234
|
+
});
|
|
235
|
+
const [wrongMintReceiverATA] = await findAssociatedTokenPda({
|
|
236
|
+
mint: wrongMint.address,
|
|
237
|
+
owner: f.receiver.address,
|
|
238
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
239
|
+
});
|
|
240
|
+
const wrongMintIx = getTransferCheckedInstruction({
|
|
241
|
+
source: wrongMintSenderATA,
|
|
242
|
+
mint: wrongMint.address,
|
|
243
|
+
destination: wrongMintReceiverATA,
|
|
244
|
+
authority: f.sender.address,
|
|
245
|
+
amount: f.amount,
|
|
246
|
+
decimals: f.decimals,
|
|
247
|
+
});
|
|
248
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongMintIx], f.facilitator);
|
|
249
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
250
|
+
t.end();
|
|
251
|
+
});
|
|
252
|
+
await t.test("rejects transaction with wrong destination", async (t) => {
|
|
253
|
+
const f = await createFixtures();
|
|
254
|
+
const wrongReceiver = await generateKeyPairSigner();
|
|
255
|
+
const [wrongReceiverATA] = await findAssociatedTokenPda({
|
|
256
|
+
mint: f.mint.address,
|
|
257
|
+
owner: wrongReceiver.address,
|
|
258
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
|
|
259
|
+
});
|
|
260
|
+
const wrongDestIx = getTransferCheckedInstruction({
|
|
261
|
+
source: f.senderATA,
|
|
262
|
+
mint: f.mint.address,
|
|
263
|
+
destination: wrongReceiverATA,
|
|
264
|
+
authority: f.sender.address,
|
|
265
|
+
amount: f.amount,
|
|
266
|
+
decimals: f.decimals,
|
|
267
|
+
});
|
|
268
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, wrongDestIx], f.facilitator);
|
|
269
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
270
|
+
t.end();
|
|
271
|
+
});
|
|
272
|
+
await t.test("rejects transaction where facilitator is transfer authority", async (t) => {
|
|
273
|
+
const f = await createFixtures();
|
|
274
|
+
const badIx = getTransferCheckedInstruction({
|
|
275
|
+
source: f.senderATA,
|
|
276
|
+
mint: f.mint.address,
|
|
277
|
+
destination: f.receiverATA,
|
|
278
|
+
authority: f.facilitator.address,
|
|
279
|
+
amount: f.amount,
|
|
280
|
+
decimals: f.decimals,
|
|
281
|
+
});
|
|
282
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, badIx], f.facilitator);
|
|
283
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
284
|
+
t.end();
|
|
285
|
+
});
|
|
286
|
+
await t.test("rejects transaction where source is facilitator ATA", async (t) => {
|
|
287
|
+
const f = await createFixtures();
|
|
288
|
+
const badIx = getTransferCheckedInstruction({
|
|
289
|
+
source: f.facilitatorATA,
|
|
290
|
+
mint: f.mint.address,
|
|
291
|
+
destination: f.receiverATA,
|
|
292
|
+
authority: f.sender.address,
|
|
293
|
+
amount: f.amount,
|
|
294
|
+
decimals: f.decimals,
|
|
295
|
+
});
|
|
296
|
+
const txMsg = buildTxMessage([f.computeLimitIx, f.computePriceIx, badIx], f.facilitator);
|
|
297
|
+
t.equal(await isValidTransaction(txMsg, f.requirements, f.facilitator.address), false);
|
|
298
|
+
t.end();
|
|
299
|
+
});
|
|
300
|
+
t.end();
|
|
301
|
+
});
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Solana Payment Package
|
|
3
|
+
* @sidebarTitle Payment Solana
|
|
4
|
+
* @description Solana payment handlers for SPL tokens and exact payment schemes
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @title Solana Exact Payment Scheme
|
|
9
|
+
* @sidebarTitle Payment Solana / Exact
|
|
10
|
+
* @description SPL token transfer payment handlers for Solana
|
|
11
|
+
*/
|
|
1
12
|
export * as exact from "./exact/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* @title SPL Token Utilities
|
|
15
|
+
* @sidebarTitle Payment Solana / SPL Token
|
|
16
|
+
* @description SPL token balance and account utilities
|
|
17
|
+
*/
|
|
2
18
|
export * as splToken from "./splToken.js";
|
|
3
19
|
//# sourceMappingURL=index.d.ts.map
|
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,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,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,QAAQ,MAAM,YAAY,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Solana Payment Package
|
|
3
|
+
* @sidebarTitle Payment Solana
|
|
4
|
+
* @description Solana payment handlers for SPL tokens and exact payment schemes
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @title Solana Exact Payment Scheme
|
|
9
|
+
* @sidebarTitle Payment Solana / Exact
|
|
10
|
+
* @description SPL token transfer payment handlers for Solana
|
|
11
|
+
*/
|
|
1
12
|
export * as exact from "./exact/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* @title SPL Token Utilities
|
|
15
|
+
* @sidebarTitle Payment Solana / SPL Token
|
|
16
|
+
* @description SPL token balance and account utilities
|
|
17
|
+
*/
|
|
2
18
|
export * as splToken from "./splToken.js";
|
package/dist/src/splToken.d.ts
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
import type { Rpc } from "@solana/rpc";
|
|
2
2
|
import type { GetTokenAccountBalanceApi } from "@solana/rpc-api";
|
|
3
3
|
import { Base58Address } from "@faremeter/types/solana";
|
|
4
|
+
/**
|
|
5
|
+
* Arguments for retrieving an SPL token balance.
|
|
6
|
+
*/
|
|
4
7
|
export interface GetTokenBalanceArgs {
|
|
8
|
+
/** The SPL token mint address */
|
|
5
9
|
asset: Base58Address;
|
|
10
|
+
/** The wallet address to check the balance for */
|
|
6
11
|
account: Base58Address;
|
|
12
|
+
/** Solana RPC client with token balance API support */
|
|
7
13
|
rpcClient: Rpc<GetTokenAccountBalanceApi>;
|
|
8
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Checks if an error indicates a token account was not found.
|
|
17
|
+
*
|
|
18
|
+
* This handles various error formats from Solana RPC responses,
|
|
19
|
+
* including TokenAccountNotFoundError and AccountNotFoundError names,
|
|
20
|
+
* as well as message-based detection.
|
|
21
|
+
*
|
|
22
|
+
* @param e - The error to check
|
|
23
|
+
* @returns True if the error indicates the account does not exist
|
|
24
|
+
*/
|
|
9
25
|
export declare function isAccountNotFoundError(e: unknown): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Retrieves the SPL token balance for an account.
|
|
28
|
+
*
|
|
29
|
+
* Looks up the associated token account (ATA) for the given wallet and
|
|
30
|
+
* mint, then fetches the token balance. Returns null if the account
|
|
31
|
+
* does not exist.
|
|
32
|
+
*
|
|
33
|
+
* @param args - The asset, account, and RPC client
|
|
34
|
+
* @returns The balance amount and decimals, or null if the account does not exist
|
|
35
|
+
*/
|
|
10
36
|
export declare function getTokenBalance(args: GetTokenBalanceArgs): Promise<{
|
|
11
37
|
amount: bigint;
|
|
12
38
|
decimals: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splToken.d.ts","sourceRoot":"","sources":["../../src/splToken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAEjE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,GAAG,CAAC,yBAAyB,CAAC,CAAC;CAC3C;
|
|
1
|
+
{"version":3,"file":"splToken.d.ts","sourceRoot":"","sources":["../../src/splToken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAEjE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,kDAAkD;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,uDAAuD;IACvD,SAAS,EAAE,GAAG,CAAC,yBAAyB,CAAC,CAAC;CAC3C;AAED;;;;;;;;;GASG;AAEH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,OAAO,WAkBhD;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB;;;UA4B9D"}
|
package/dist/src/splToken.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { address } from "@solana/addresses";
|
|
2
2
|
import { Base58Address } from "@faremeter/types/solana";
|
|
3
3
|
import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, } from "@solana-program/token";
|
|
4
|
+
/**
|
|
5
|
+
* Checks if an error indicates a token account was not found.
|
|
6
|
+
*
|
|
7
|
+
* This handles various error formats from Solana RPC responses,
|
|
8
|
+
* including TokenAccountNotFoundError and AccountNotFoundError names,
|
|
9
|
+
* as well as message-based detection.
|
|
10
|
+
*
|
|
11
|
+
* @param e - The error to check
|
|
12
|
+
* @returns True if the error indicates the account does not exist
|
|
13
|
+
*/
|
|
4
14
|
// XXX - There has got to be a better way to do this.
|
|
5
15
|
export function isAccountNotFoundError(e) {
|
|
6
16
|
if (!e || !(e instanceof Error)) {
|
|
@@ -16,6 +26,16 @@ export function isAccountNotFoundError(e) {
|
|
|
16
26
|
}
|
|
17
27
|
return false;
|
|
18
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Retrieves the SPL token balance for an account.
|
|
31
|
+
*
|
|
32
|
+
* Looks up the associated token account (ATA) for the given wallet and
|
|
33
|
+
* mint, then fetches the token balance. Returns null if the account
|
|
34
|
+
* does not exist.
|
|
35
|
+
*
|
|
36
|
+
* @param args - The asset, account, and RPC client
|
|
37
|
+
* @returns The balance amount and decimals, or null if the account does not exist
|
|
38
|
+
*/
|
|
19
39
|
export async function getTokenBalance(args) {
|
|
20
40
|
const { asset, account, rpcClient } = args;
|
|
21
41
|
const owner = address(account);
|