@faremeter/payment-solana 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/charge/client.d.ts +10 -6
- package/dist/src/charge/client.d.ts.map +1 -1
- package/dist/src/charge/client.js +117 -102
- package/dist/src/charge/common.d.ts +3 -3
- package/dist/src/charge/server.d.ts +18 -7
- package/dist/src/charge/server.d.ts.map +1 -1
- package/dist/src/charge/server.js +22 -24
- package/dist/src/compat.d.ts +38 -0
- package/dist/src/compat.d.ts.map +1 -0
- package/dist/src/compat.js +86 -0
- package/dist/src/compat.test.d.ts +3 -0
- package/dist/src/compat.test.d.ts.map +1 -0
- package/dist/src/compat.test.js +70 -0
- package/dist/src/exact/client.d.ts +18 -15
- package/dist/src/exact/client.d.ts.map +1 -1
- package/dist/src/exact/client.js +124 -96
- package/dist/src/exact/common.d.ts +1 -1
- package/dist/src/exact/facilitator.d.ts +19 -12
- package/dist/src/exact/facilitator.d.ts.map +1 -1
- package/dist/src/exact/facilitator.js +19 -18
- package/dist/src/exact/memo.d.ts +0 -2
- package/dist/src/exact/memo.d.ts.map +1 -1
- package/dist/src/exact/memo.js +0 -9
- package/dist/src/exact/verify.d.ts +5 -1
- package/dist/src/exact/verify.d.ts.map +1 -1
- package/dist/src/exact/verify.js +8 -2
- package/dist/src/exact/verify.test.js +80 -3
- package/dist/src/flex/client/handler.d.ts +31 -0
- package/dist/src/flex/client/handler.d.ts.map +1 -0
- package/dist/src/flex/client/handler.js +104 -0
- package/dist/src/flex/client/index.d.ts +3 -0
- package/dist/src/flex/client/index.d.ts.map +1 -0
- package/dist/src/flex/client/index.js +1 -0
- package/dist/src/flex/common.d.ts +15 -0
- package/dist/src/flex/common.d.ts.map +1 -0
- package/dist/src/flex/common.js +7 -0
- package/dist/src/flex/facilitator/handler.d.ts +48 -0
- package/dist/src/flex/facilitator/handler.d.ts.map +1 -0
- package/dist/src/flex/facilitator/handler.js +705 -0
- package/dist/src/flex/facilitator/index.d.ts +5 -0
- package/dist/src/flex/facilitator/index.d.ts.map +1 -0
- package/dist/src/flex/facilitator/index.js +2 -0
- package/dist/src/flex/hono/index.d.ts +3 -0
- package/dist/src/flex/hono/index.d.ts.map +1 -0
- package/dist/src/flex/hono/index.js +1 -0
- package/dist/src/flex/hono/upto-handler.d.ts +20 -0
- package/dist/src/flex/hono/upto-handler.d.ts.map +1 -0
- package/dist/src/flex/hono/upto-handler.js +72 -0
- package/dist/src/flex/hono/upto-handler.test.d.ts +3 -0
- package/dist/src/flex/hono/upto-handler.test.d.ts.map +1 -0
- package/dist/src/flex/hono/upto-handler.test.js +381 -0
- package/dist/src/flex/index.d.ts +4 -0
- package/dist/src/flex/index.d.ts.map +1 -0
- package/dist/src/flex/index.js +3 -0
- package/dist/src/flex/logger.d.ts +2 -0
- package/dist/src/flex/logger.d.ts.map +1 -0
- package/dist/src/flex/logger.js +2 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -7
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env pnpm tsx
|
|
2
|
+
import t from "tap";
|
|
3
|
+
import { address, createKeyPairSignerFromBytes, createSolanaRpc, } from "@solana/kit";
|
|
4
|
+
import { toAddress, toKeyPairSigner, toRpc } from "./compat.js";
|
|
5
|
+
// A deterministic 64-byte secret key for testing (32-byte seed +
|
|
6
|
+
// 32-byte public key, as expected by createKeyPairSignerFromBytes).
|
|
7
|
+
// Generated once from a known seed; the address is deterministic.
|
|
8
|
+
const TEST_SECRET_KEY = new Uint8Array([
|
|
9
|
+
174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56,
|
|
10
|
+
222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, 15,
|
|
11
|
+
185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, 121,
|
|
12
|
+
35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135,
|
|
13
|
+
]);
|
|
14
|
+
// ---------- toAddress ----------
|
|
15
|
+
await t.test("toAddress: passes through a kit Address string", async (t) => {
|
|
16
|
+
const addr = address("11111111111111111111111111111111");
|
|
17
|
+
const result = toAddress(addr);
|
|
18
|
+
t.equal(result, addr);
|
|
19
|
+
});
|
|
20
|
+
await t.test("toAddress: converts a PublicKey-like object", async (t) => {
|
|
21
|
+
const fakePublicKey = {
|
|
22
|
+
toBase58: () => "11111111111111111111111111111111",
|
|
23
|
+
};
|
|
24
|
+
const result = toAddress(fakePublicKey);
|
|
25
|
+
t.equal(result, "11111111111111111111111111111111");
|
|
26
|
+
});
|
|
27
|
+
await t.test("toAddress: throws on invalid input", async (t) => {
|
|
28
|
+
t.throws(() => toAddress(42), {
|
|
29
|
+
message: /expected an Address string or PublicKey/,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
// ---------- toKeyPairSigner ----------
|
|
33
|
+
await t.test("toKeyPairSigner: passes through a kit KeyPairSigner", async (t) => {
|
|
34
|
+
const signer = await createKeyPairSignerFromBytes(TEST_SECRET_KEY);
|
|
35
|
+
const result = await toKeyPairSigner(signer);
|
|
36
|
+
t.equal(result.address, signer.address);
|
|
37
|
+
t.ok("signMessages" in result);
|
|
38
|
+
});
|
|
39
|
+
await t.test("toKeyPairSigner: converts a Uint8Array secret key", async (t) => {
|
|
40
|
+
const expected = await createKeyPairSignerFromBytes(TEST_SECRET_KEY);
|
|
41
|
+
const result = await toKeyPairSigner(TEST_SECRET_KEY);
|
|
42
|
+
t.equal(result.address, expected.address);
|
|
43
|
+
t.ok("signMessages" in result);
|
|
44
|
+
});
|
|
45
|
+
await t.test("toKeyPairSigner: converts a Keypair-like object", async (t) => {
|
|
46
|
+
const expected = await createKeyPairSignerFromBytes(TEST_SECRET_KEY);
|
|
47
|
+
// Duck-type that matches @solana/web3.js v1 Keypair shape
|
|
48
|
+
const fakeKeypair = {
|
|
49
|
+
secretKey: TEST_SECRET_KEY,
|
|
50
|
+
publicKey: { toBase58: () => expected.address },
|
|
51
|
+
};
|
|
52
|
+
const result = await toKeyPairSigner(fakeKeypair);
|
|
53
|
+
t.equal(result.address, expected.address);
|
|
54
|
+
t.ok("signMessages" in result);
|
|
55
|
+
});
|
|
56
|
+
await t.test("toKeyPairSigner: throws on invalid input", async (t) => {
|
|
57
|
+
await t.rejects(() => toKeyPairSigner("not-a-signer"), {
|
|
58
|
+
message: /expected a Uint8Array, KeyPairSigner, or Keypair/,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
// ---------- toRpc ----------
|
|
62
|
+
await t.test("toRpc: passes through an Rpc object", async (t) => {
|
|
63
|
+
const rpc = createSolanaRpc("https://api.devnet.solana.com");
|
|
64
|
+
const result = toRpc(rpc);
|
|
65
|
+
t.equal(result, rpc);
|
|
66
|
+
});
|
|
67
|
+
await t.test("toRpc: creates an Rpc from a URL string", async (t) => {
|
|
68
|
+
const result = toRpc("https://api.devnet.solana.com");
|
|
69
|
+
t.ok(typeof result.getLatestBlockhash === "function");
|
|
70
|
+
});
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import type { PaymentHandler } from "@faremeter/types/client";
|
|
2
2
|
import type { SolanaCAIP2Network } from "@faremeter/info/solana";
|
|
3
|
-
import {
|
|
3
|
+
import { type Address, type Blockhash, type Instruction, type Rpc, type SolanaRpcApi, type Transaction } from "@solana/kit";
|
|
4
|
+
export type WalletLifetimeConstraint = {
|
|
5
|
+
blockhash: Blockhash;
|
|
6
|
+
lastValidBlockHeight: bigint;
|
|
7
|
+
};
|
|
4
8
|
export type Wallet = {
|
|
5
9
|
network: string | SolanaCAIP2Network;
|
|
6
|
-
publicKey:
|
|
7
|
-
buildTransaction?: (instructions:
|
|
8
|
-
partiallySignTransaction?: (tx:
|
|
9
|
-
|
|
10
|
-
sendTransaction?: (tx: VersionedTransaction) => Promise<string>;
|
|
10
|
+
publicKey: Address;
|
|
11
|
+
buildTransaction?: (instructions: readonly Instruction[], lifetimeConstraint: WalletLifetimeConstraint) => Promise<Transaction>;
|
|
12
|
+
partiallySignTransaction?: (tx: Transaction) => Promise<Transaction>;
|
|
13
|
+
sendTransaction?: (tx: Transaction) => Promise<string>;
|
|
11
14
|
};
|
|
12
|
-
interface GetAssociatedTokenAddressSyncOptions {
|
|
13
|
-
allowOwnerOffCurve?: boolean;
|
|
14
|
-
programId?: PublicKey;
|
|
15
|
-
associatedTokenProgramId?: PublicKey;
|
|
16
|
-
}
|
|
17
15
|
interface CreatePaymentHandlerOptions {
|
|
18
|
-
token?:
|
|
16
|
+
token?: {
|
|
17
|
+
programId?: Address;
|
|
18
|
+
};
|
|
19
19
|
settlementRentDestination?: string;
|
|
20
20
|
features?: {
|
|
21
21
|
enableSettlementAccounts?: boolean;
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
export declare function buildAndSignClientTransaction(wallet: Wallet, instructions: readonly Instruction[], payerKey: Address, lifetimeConstraint: WalletLifetimeConstraint): Promise<Transaction>;
|
|
24
25
|
/**
|
|
25
26
|
* Creates a payment handler for the Solana exact payment scheme.
|
|
26
27
|
*
|
|
@@ -28,11 +29,13 @@ interface CreatePaymentHandlerOptions {
|
|
|
28
29
|
* and submitted by the client to fulfill x402 payment requirements.
|
|
29
30
|
*
|
|
30
31
|
* @param wallet - Wallet providing signing capabilities
|
|
31
|
-
* @param
|
|
32
|
-
* @param
|
|
32
|
+
* @param mintInput - SPL token mint address
|
|
33
|
+
* @param rpcInput - Optional Solana RPC client for fetching blockhash and mint info
|
|
33
34
|
* @param options - Optional configuration for token address and features
|
|
34
35
|
* @returns A PaymentHandler function for use with the x402 client
|
|
35
36
|
*/
|
|
36
|
-
export declare function createPaymentHandler(wallet: Wallet,
|
|
37
|
+
export declare function createPaymentHandler(wallet: Wallet, mintInput: Address | {
|
|
38
|
+
toBase58(): string;
|
|
39
|
+
}, rpcInput?: Rpc<SolanaRpcApi> | string, options?: CreatePaymentHandlerOptions): PaymentHandler;
|
|
37
40
|
export {};
|
|
38
41
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/exact/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,cAAc,EAEf,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/exact/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,cAAc,EAEf,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAYjE,OAAO,EAYL,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,GAAG,EACR,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAOrB,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,SAAS,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAAC;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,CACjB,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,kBAAkB,EAAE,wBAAwB,KACzC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1B,wBAAwB,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACrE,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD,CAAC;AAoFF,UAAU,2BAA2B;IACnC,KAAK,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;IACF,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,EAAE;QACT,wBAAwB,CAAC,EAAE,OAAO,CAAC;KACpC,CAAC;CACH;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,SAAS,WAAW,EAAE,EACpC,QAAQ,EAAE,OAAO,EACjB,kBAAkB,EAAE,wBAAwB,GAC3C,OAAO,CAAC,WAAW,CAAC,CAsBtB;AAwBD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,OAAO,GAAG;IAAE,QAAQ,IAAI,MAAM,CAAA;CAAE,EAC3C,QAAQ,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,GAAG,MAAM,EACrC,OAAO,CAAC,EAAE,2BAA2B,GACpC,cAAc,CAwJhB"}
|
package/dist/src/exact/client.js
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
import { isValidationError, throwValidationError } from "@faremeter/types";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { fetchMint, findAssociatedTokenPda, getCreateAssociatedTokenIdempotentInstruction, getTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS, } from "@solana-program/token";
|
|
3
|
+
import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction, } from "@solana-program/compute-budget";
|
|
4
|
+
import { address, appendTransactionMessageInstructions, compileTransaction, createKeyPairFromPrivateKeyBytes, createKeyPairSignerFromBytes, createNoopSigner, createTransactionMessage, getBase64EncodedWireTransaction, pipe, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, } from "@solana/kit";
|
|
5
5
|
import { PaymentRequirementsExtra } from "./facilitator.js";
|
|
6
6
|
import { generateMatcher } from "./common.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
const { allowOwnerOffCurve, programId, associatedTokenProgramId } = tokenConfig;
|
|
11
|
-
// NOTE: These map to the trailing default args of
|
|
12
|
-
// getAssociatedTokenAddressSync, so order matters. If things are
|
|
13
|
-
// refactored, they should be updated to match the reality of the
|
|
14
|
-
// implementation.
|
|
15
|
-
return [allowOwnerOffCurve, programId, associatedTokenProgramId];
|
|
16
|
-
}
|
|
7
|
+
import { generateMemoNonce } from "./memo.js";
|
|
8
|
+
import { getAddMemoInstruction } from "@solana-program/memo";
|
|
9
|
+
import { toAddress, toRpc } from "../compat.js";
|
|
17
10
|
const PaymentMode = {
|
|
18
11
|
ToSpec: "toSpec",
|
|
19
12
|
SettlementAccount: "settlementAccount",
|
|
20
13
|
};
|
|
21
14
|
async function extractMetadata(args) {
|
|
22
|
-
const {
|
|
15
|
+
const { rpc, mint, requirements, wallet, options } = args;
|
|
23
16
|
const extra = PaymentRequirementsExtra(requirements.extra);
|
|
24
17
|
if (isValidationError(extra)) {
|
|
25
18
|
throwValidationError("couldn't validate requirements extra field", extra);
|
|
26
19
|
}
|
|
27
|
-
let
|
|
20
|
+
let lifetimeConstraint;
|
|
28
21
|
if (extra.recentBlockhash !== undefined) {
|
|
29
|
-
|
|
22
|
+
// The server supplied a blockhash but no lastValidBlockHeight. Kit
|
|
23
|
+
// requires both; use a sentinel lastValidBlockHeight of 0n since it
|
|
24
|
+
// only affects client-side retry timing, not wire format.
|
|
25
|
+
lifetimeConstraint = {
|
|
26
|
+
blockhash: extra.recentBlockhash,
|
|
27
|
+
lastValidBlockHeight: 0n,
|
|
28
|
+
};
|
|
30
29
|
}
|
|
31
|
-
else if (
|
|
32
|
-
|
|
30
|
+
else if (rpc !== undefined) {
|
|
31
|
+
const { value } = await rpc.getLatestBlockhash().send();
|
|
32
|
+
lifetimeConstraint = {
|
|
33
|
+
blockhash: value.blockhash,
|
|
34
|
+
lastValidBlockHeight: value.lastValidBlockHeight,
|
|
35
|
+
};
|
|
33
36
|
}
|
|
34
37
|
else {
|
|
35
38
|
throw new Error("couldn't get the latest Solana network block hash");
|
|
@@ -38,15 +41,15 @@ async function extractMetadata(args) {
|
|
|
38
41
|
if (extra.decimals !== undefined) {
|
|
39
42
|
decimals = extra.decimals;
|
|
40
43
|
}
|
|
41
|
-
else if (
|
|
42
|
-
const mintInfo = await
|
|
43
|
-
decimals = mintInfo.decimals;
|
|
44
|
+
else if (rpc !== undefined) {
|
|
45
|
+
const mintInfo = await fetchMint(rpc, mint);
|
|
46
|
+
decimals = mintInfo.data.decimals;
|
|
44
47
|
}
|
|
45
48
|
else {
|
|
46
49
|
throw new Error("couldn't get the decimal information for the mint");
|
|
47
50
|
}
|
|
48
|
-
const payerKey =
|
|
49
|
-
const payTo =
|
|
51
|
+
const payerKey = address(extra.feePayer);
|
|
52
|
+
const payTo = address(requirements.payTo);
|
|
50
53
|
const amount = Number(requirements.amount);
|
|
51
54
|
let paymentMode = PaymentMode.ToSpec;
|
|
52
55
|
if (options?.features?.enableSettlementAccounts &&
|
|
@@ -55,11 +58,11 @@ async function extractMetadata(args) {
|
|
|
55
58
|
paymentMode = PaymentMode.SettlementAccount;
|
|
56
59
|
}
|
|
57
60
|
const tokenProgramId = extra.tokenProgram
|
|
58
|
-
?
|
|
59
|
-
: (options?.token?.programId ??
|
|
61
|
+
? address(extra.tokenProgram)
|
|
62
|
+
: (options?.token?.programId ?? TOKEN_PROGRAM_ADDRESS);
|
|
60
63
|
const memo = extra.memo;
|
|
61
64
|
return {
|
|
62
|
-
|
|
65
|
+
lifetimeConstraint,
|
|
63
66
|
decimals,
|
|
64
67
|
payTo,
|
|
65
68
|
amount,
|
|
@@ -69,6 +72,39 @@ async function extractMetadata(args) {
|
|
|
69
72
|
memo,
|
|
70
73
|
};
|
|
71
74
|
}
|
|
75
|
+
export async function buildAndSignClientTransaction(wallet, instructions, payerKey, lifetimeConstraint) {
|
|
76
|
+
const sign = async (tx) => {
|
|
77
|
+
if (wallet.partiallySignTransaction) {
|
|
78
|
+
return wallet.partiallySignTransaction(tx);
|
|
79
|
+
}
|
|
80
|
+
return tx;
|
|
81
|
+
};
|
|
82
|
+
let tx;
|
|
83
|
+
if (wallet.buildTransaction) {
|
|
84
|
+
tx = await wallet.buildTransaction(instructions, lifetimeConstraint);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const message = pipe(createTransactionMessage({ version: 0 }), (m) => setTransactionMessageFeePayer(payerKey, m), (m) => setTransactionMessageLifetimeUsingBlockhash(lifetimeConstraint, m), (m) => appendTransactionMessageInstructions(instructions, m));
|
|
88
|
+
tx = compileTransaction(message);
|
|
89
|
+
}
|
|
90
|
+
return sign(tx);
|
|
91
|
+
}
|
|
92
|
+
async function generateSettleSigner() {
|
|
93
|
+
// Build a fresh ed25519 keypair locally and also return its wire-format
|
|
94
|
+
// bytes (32-byte privkey || 32-byte pubkey) so the facilitator can
|
|
95
|
+
// reconstruct the signer on settlement via createKeyPairSignerFromBytes.
|
|
96
|
+
const privateKeyBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
97
|
+
// @solana/keys' CryptoKeyPair is returned here; the lib in our tsconfig
|
|
98
|
+
// doesn't include DOM types so we widen to a minimal local shape.
|
|
99
|
+
const keyPair = (await createKeyPairFromPrivateKeyBytes(privateKeyBytes,
|
|
100
|
+
/* extractable */ true));
|
|
101
|
+
const publicKeyBytes = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
|
|
102
|
+
const secretKey = new Uint8Array(64);
|
|
103
|
+
secretKey.set(privateKeyBytes);
|
|
104
|
+
secretKey.set(publicKeyBytes, 32);
|
|
105
|
+
const signer = await createKeyPairSignerFromBytes(secretKey);
|
|
106
|
+
return { signer, secretKey };
|
|
107
|
+
}
|
|
72
108
|
/**
|
|
73
109
|
* Creates a payment handler for the Solana exact payment scheme.
|
|
74
110
|
*
|
|
@@ -76,101 +112,93 @@ async function extractMetadata(args) {
|
|
|
76
112
|
* and submitted by the client to fulfill x402 payment requirements.
|
|
77
113
|
*
|
|
78
114
|
* @param wallet - Wallet providing signing capabilities
|
|
79
|
-
* @param
|
|
80
|
-
* @param
|
|
115
|
+
* @param mintInput - SPL token mint address
|
|
116
|
+
* @param rpcInput - Optional Solana RPC client for fetching blockhash and mint info
|
|
81
117
|
* @param options - Optional configuration for token address and features
|
|
82
118
|
* @returns A PaymentHandler function for use with the x402 client
|
|
83
119
|
*/
|
|
84
|
-
export function createPaymentHandler(wallet,
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
if (wallet.partiallySignTransaction) {
|
|
89
|
-
return wallet.partiallySignTransaction(tx);
|
|
90
|
-
}
|
|
91
|
-
if (wallet.updateTransaction) {
|
|
92
|
-
if (!hasWarnedAboutDeprecation) {
|
|
93
|
-
logger.warning("wallet.partiallySignTransaction is not available, falling back to updateTransaction");
|
|
94
|
-
hasWarnedAboutDeprecation = true;
|
|
95
|
-
}
|
|
96
|
-
return wallet.updateTransaction(tx);
|
|
97
|
-
}
|
|
98
|
-
return tx;
|
|
99
|
-
};
|
|
100
|
-
const { isMatchingRequirement } = generateMatcher(wallet.network, mint ? mint.toBase58() : "sol");
|
|
120
|
+
export function createPaymentHandler(wallet, mintInput, rpcInput, options) {
|
|
121
|
+
const mint = toAddress(mintInput);
|
|
122
|
+
const rpc = rpcInput ? toRpc(rpcInput) : undefined;
|
|
123
|
+
const { isMatchingRequirement } = generateMatcher(wallet.network, mint);
|
|
101
124
|
return async (_context, accepts) => {
|
|
102
125
|
const compatibleRequirements = accepts.filter(isMatchingRequirement);
|
|
103
126
|
const res = compatibleRequirements.map((requirements) => {
|
|
104
127
|
const exec = async () => {
|
|
105
|
-
const {
|
|
106
|
-
|
|
128
|
+
const { lifetimeConstraint, decimals, payTo, amount, payerKey, paymentMode, tokenProgramId, memo, } = await extractMetadata({
|
|
129
|
+
rpc,
|
|
107
130
|
mint,
|
|
108
131
|
requirements,
|
|
109
132
|
wallet,
|
|
110
133
|
options,
|
|
111
134
|
});
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
const instructions = [
|
|
117
|
-
ComputeBudgetProgram.setComputeUnitLimit({
|
|
118
|
-
units: 50_000,
|
|
119
|
-
}),
|
|
120
|
-
ComputeBudgetProgram.setComputeUnitPrice({
|
|
121
|
-
microLamports: 1,
|
|
122
|
-
}),
|
|
135
|
+
const baseInstructions = [
|
|
136
|
+
getSetComputeUnitLimitInstruction({ units: 50_000 }),
|
|
137
|
+
getSetComputeUnitPriceInstruction({ microLamports: 1n }),
|
|
123
138
|
];
|
|
124
|
-
const
|
|
139
|
+
const walletSigner = createNoopSigner(wallet.publicKey);
|
|
140
|
+
const [sourceAccount] = await findAssociatedTokenPda({
|
|
141
|
+
mint,
|
|
142
|
+
owner: wallet.publicKey,
|
|
143
|
+
tokenProgram: tokenProgramId,
|
|
144
|
+
});
|
|
125
145
|
switch (paymentMode) {
|
|
126
146
|
case PaymentMode.ToSpec: {
|
|
127
|
-
const receiverAccount =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
tx = await wallet.buildTransaction(instructions, recentBlockhash);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
const message = new TransactionMessage({
|
|
135
|
-
instructions,
|
|
136
|
-
payerKey,
|
|
137
|
-
recentBlockhash,
|
|
138
|
-
}).compileToV0Message();
|
|
139
|
-
tx = new VersionedTransaction(message);
|
|
140
|
-
}
|
|
141
|
-
tx = await signTransaction(tx);
|
|
142
|
-
const base64EncodedWireTransaction = getBase64EncodedWireTransaction({
|
|
143
|
-
messageBytes: tx.message.serialize(),
|
|
144
|
-
signatures: tx.signatures,
|
|
147
|
+
const [receiverAccount] = await findAssociatedTokenPda({
|
|
148
|
+
mint,
|
|
149
|
+
owner: payTo,
|
|
150
|
+
tokenProgram: tokenProgramId,
|
|
145
151
|
});
|
|
152
|
+
const instructions = [
|
|
153
|
+
...baseInstructions,
|
|
154
|
+
getTransferCheckedInstruction({
|
|
155
|
+
source: sourceAccount,
|
|
156
|
+
mint,
|
|
157
|
+
destination: receiverAccount,
|
|
158
|
+
authority: walletSigner,
|
|
159
|
+
amount,
|
|
160
|
+
decimals,
|
|
161
|
+
}, { programAddress: tokenProgramId }),
|
|
162
|
+
getAddMemoInstruction({ memo: memo ?? generateMemoNonce() }),
|
|
163
|
+
];
|
|
164
|
+
const tx = await buildAndSignClientTransaction(wallet, instructions, payerKey, lifetimeConstraint);
|
|
146
165
|
const payload = {
|
|
147
|
-
transaction:
|
|
166
|
+
transaction: getBase64EncodedWireTransaction(tx),
|
|
148
167
|
};
|
|
149
168
|
return { payload };
|
|
150
169
|
}
|
|
151
170
|
case PaymentMode.SettlementAccount: {
|
|
152
|
-
const
|
|
153
|
-
const settleATA =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
171
|
+
const { signer: settleSigner, secretKey: settleSecretBytes } = await generateSettleSigner();
|
|
172
|
+
const [settleATA] = await findAssociatedTokenPda({
|
|
173
|
+
mint,
|
|
174
|
+
owner: settleSigner.address,
|
|
175
|
+
tokenProgram: tokenProgramId,
|
|
176
|
+
});
|
|
177
|
+
const instructions = [
|
|
178
|
+
...baseInstructions,
|
|
179
|
+
getCreateAssociatedTokenIdempotentInstruction({
|
|
180
|
+
ata: settleATA,
|
|
181
|
+
owner: settleSigner.address,
|
|
182
|
+
payer: walletSigner,
|
|
183
|
+
mint,
|
|
184
|
+
tokenProgram: tokenProgramId,
|
|
185
|
+
}),
|
|
186
|
+
getTransferCheckedInstruction({
|
|
187
|
+
source: sourceAccount,
|
|
188
|
+
mint,
|
|
189
|
+
destination: settleATA,
|
|
190
|
+
authority: walletSigner,
|
|
191
|
+
amount,
|
|
192
|
+
decimals,
|
|
193
|
+
}, { programAddress: tokenProgramId }),
|
|
194
|
+
];
|
|
195
|
+
const tx = await buildAndSignClientTransaction(wallet, instructions, payerKey, lifetimeConstraint);
|
|
168
196
|
if (!wallet.sendTransaction) {
|
|
169
197
|
throw new Error("wallet must support sending transactions to use settlement accounts with exact");
|
|
170
198
|
}
|
|
171
199
|
const transactionSignature = await wallet.sendTransaction(tx);
|
|
172
|
-
const settleSecretKey = Buffer.from(
|
|
173
|
-
const settlementRentDestination = options?.settlementRentDestination ?? wallet.publicKey
|
|
200
|
+
const settleSecretKey = Buffer.from(settleSecretBytes).toString("base64");
|
|
201
|
+
const settlementRentDestination = options?.settlementRentDestination ?? wallet.publicKey;
|
|
174
202
|
const payload = {
|
|
175
203
|
settleSecretKey,
|
|
176
204
|
transactionSignature,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type SolanaCAIP2Network } from "@faremeter/info/solana";
|
|
2
2
|
export declare const x402Scheme = "exact";
|
|
3
3
|
export declare function generateMatcher(network: string | SolanaCAIP2Network, asset: string): {
|
|
4
|
-
matchTuple: import("arktype/internal/
|
|
4
|
+
matchTuple: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
5
5
|
scheme: (In: string) => import("arktype/internal/attributes.ts").To<Lowercase<string>>;
|
|
6
6
|
network: (In: string) => import("arktype/internal/attributes.ts").To<Lowercase<string>>;
|
|
7
7
|
asset: (In: string) => import("arktype/internal/attributes.ts").To<Lowercase<string>>;
|
|
@@ -2,15 +2,14 @@ import { type x402PaymentRequirements, type x402PaymentPayload, type x402SettleR
|
|
|
2
2
|
import type { FacilitatorHandler } from "@faremeter/types/facilitator";
|
|
3
3
|
import { type SolanaCAIP2Network } from "@faremeter/info/solana";
|
|
4
4
|
import { fetchMint } from "@solana-program/token";
|
|
5
|
-
import { type Rpc, type SolanaRpcApi } from "@solana/kit";
|
|
5
|
+
import { type Address, type KeyPairSigner, type Rpc, type SolanaRpcApi } from "@solana/kit";
|
|
6
6
|
import type { TransactionError } from "@solana/rpc-types";
|
|
7
|
-
import { Keypair, PublicKey } from "@solana/web3.js";
|
|
8
7
|
import { logger } from "./logger.js";
|
|
9
8
|
export interface HookBaseArgs {
|
|
10
9
|
network: string | SolanaCAIP2Network;
|
|
11
10
|
rpc: Rpc<SolanaRpcApi>;
|
|
12
|
-
|
|
13
|
-
mint:
|
|
11
|
+
feePayerSigner: KeyPairSigner;
|
|
12
|
+
mint: Address;
|
|
14
13
|
mintInfo: Awaited<ReturnType<typeof fetchMint>>;
|
|
15
14
|
requirements: x402PaymentRequirements;
|
|
16
15
|
payment: x402PaymentPayload;
|
|
@@ -24,11 +23,11 @@ export interface FacilitatorHooks {
|
|
|
24
23
|
afterVerify?: HookResponseFuncs<x402VerifyResponse>;
|
|
25
24
|
afterSettle?: HookResponseFuncs<x402SettleResponse>;
|
|
26
25
|
}
|
|
27
|
-
export declare const PaymentRequirementsExtraFeatures: import("arktype/internal/
|
|
26
|
+
export declare const PaymentRequirementsExtraFeatures: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
28
27
|
xSettlementAccountSupported?: boolean;
|
|
29
28
|
}, {}>;
|
|
30
29
|
export type PaymentRequirementsExtraFeatures = typeof PaymentRequirementsExtraFeatures.infer;
|
|
31
|
-
export declare const PaymentRequirementsExtra: import("arktype/internal/
|
|
30
|
+
export declare const PaymentRequirementsExtra: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
32
31
|
feePayer: string;
|
|
33
32
|
decimals?: number;
|
|
34
33
|
recentBlockhash?: string;
|
|
@@ -43,20 +42,21 @@ interface FacilitatorOptions {
|
|
|
43
42
|
retryDelayMs?: number;
|
|
44
43
|
maxPriorityFee?: number;
|
|
45
44
|
maxTransactionAge?: number;
|
|
45
|
+
requireMemo?: boolean;
|
|
46
46
|
features?: {
|
|
47
47
|
enableSettlementAccounts?: boolean;
|
|
48
48
|
enableDuplicateCheck?: boolean;
|
|
49
49
|
};
|
|
50
50
|
hooks?: readonly FacilitatorHooks[];
|
|
51
51
|
}
|
|
52
|
-
export declare const PaymentPayloadTransaction: import("arktype/internal/
|
|
52
|
+
export declare const PaymentPayloadTransaction: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
53
53
|
transaction: (In: string) => import("arktype").Out<Readonly<{
|
|
54
54
|
messageBytes: import("@solana/transactions").TransactionMessageBytes;
|
|
55
55
|
signatures: import("@solana/transactions").SignaturesMap;
|
|
56
56
|
}>>;
|
|
57
57
|
}, {}>;
|
|
58
58
|
export type PaymentPayloadTransaction = typeof PaymentPayloadTransaction.infer;
|
|
59
|
-
export declare const PaymentPayloadSettlementAccount: import("arktype/internal/
|
|
59
|
+
export declare const PaymentPayloadSettlementAccount: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
60
60
|
transactionSignature: string;
|
|
61
61
|
settleSecretKey: (In: string) => import("arktype").Out<Uint8Array<ArrayBuffer>>;
|
|
62
62
|
settlementRentDestination?: string;
|
|
@@ -70,12 +70,19 @@ export declare function transactionErrorToString(t: TransactionError): string;
|
|
|
70
70
|
* fee payer keypair, and submits them to the Solana network.
|
|
71
71
|
*
|
|
72
72
|
* @param network - Solana network identifier (cluster name, CAIP-2 string, or SolanaCAIP2Network object)
|
|
73
|
-
* @param
|
|
74
|
-
* @param
|
|
75
|
-
* @param
|
|
73
|
+
* @param rpcInput - Solana RPC client
|
|
74
|
+
* @param feePayerSignerInput - Keypair or signer for paying transaction fees
|
|
75
|
+
* @param mintInput - SPL token mint public key
|
|
76
76
|
* @param config - Optional configuration for retries, fees, and hooks
|
|
77
77
|
* @returns A FacilitatorHandler for processing Solana exact payments
|
|
78
78
|
*/
|
|
79
|
-
export declare const createFacilitatorHandler: (network: string | SolanaCAIP2Network,
|
|
79
|
+
export declare const createFacilitatorHandler: (network: string | SolanaCAIP2Network, rpcInput: Rpc<SolanaRpcApi> | string, feePayerSignerInput: KeyPairSigner | {
|
|
80
|
+
secretKey: Uint8Array;
|
|
81
|
+
publicKey: {
|
|
82
|
+
toBase58(): string;
|
|
83
|
+
};
|
|
84
|
+
}, mintInput: Address | {
|
|
85
|
+
toBase58(): string;
|
|
86
|
+
}, config?: FacilitatorOptions) => Promise<FacilitatorHandler>;
|
|
80
87
|
export {};
|
|
81
88
|
//# sourceMappingURL=facilitator.d.ts.map
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"facilitator.d.ts","sourceRoot":"","sources":["../../../src/exact/facilitator.ts"],"names":[],"mappings":"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"}
|