-
Notifications
You must be signed in to change notification settings - Fork 95
Add e2e solana example #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- fixed some type errors - added a memo instruction that is verified in the receipt service to prevent replay attacks
gitteri
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some documentation around how Solana works for agentic payments. There are a number of references to Base / EVM in doc files
https://github.com/search?q=repo%3Aagentcommercekit%2Fack+path%3A%2F%5Edocs%5C%2F%2F++base&type=code
|
Thanks @Woody4618 - digging into this now. Going a bit slower because the Circle Faucet is not working for Solana Devnet at the moment, but will have more soon! |
demos/payments/src/index.ts
Outdated
| } | ||
| const signedPayload = await createJwt(payload, { | ||
| issuer: client.did, | ||
| signer: client.jwtSigner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of those areas where we'll need to update our docs and patterns in ACK to be a bit clearer, but the message here should be signed by the key that made the actual transaction
WalkthroughAdds Solana devnet support to the payments demo and docs: new Solana dependencies and constants, server-side Solana key generation and payment option, client Solana USDC payment flow and SOL balance helper, receipt-service Solana transaction verification, and updated documentation/examples. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
demos/payments/src/server.ts (1)
19-27: Remove unused type guard.The
isSolanaKeysResulttype guard is defined but never used. TypeScript already provides the correct typing forensureSolanaKeys()return value, making this type guard unnecessary.Apply this diff to remove the unused code:
-function isSolanaKeysResult( - v: unknown -): v is { publicKey: string; secretKeyJson: string } { - return ( - !!v && - typeof (v as { publicKey?: unknown }).publicKey === "string" && - typeof (v as { secretKeyJson?: unknown }).secretKeyJson === "string" - ) -} -Based on past review comment from venables.
🧹 Nitpick comments (4)
demos/payments/src/utils/ensure-balances.ts (1)
48-73: Consider adding error handling for RPC calls.The function correctly implements the balance-checking flow with user prompts, but RPC calls to
getBalancelack error handling. Network failures or RPC unavailability could cause unhandled exceptions.Consider wrapping the RPC call in a try-catch block:
while (lamports === BigInt(0)) { console.log("We need to fund this Solana address with devnet SOL:", address) console.log("Faucet: https://faucet.solana.com/") const prefilled = `https://faucet.solana.com/?walletAddress=${encodeURIComponent( address )}&amount=0.5` console.log("Prefilled faucet (0.5 SOL):", prefilled) console.log("Once funded, press enter to check balance again") await waitForEnter() console.log("Attempting to fetch SOL balance... " + pubkey) - ;({ value: lamports } = await rpc - .getBalance(pubkey, { commitment: solana.commitment }) - .send()) + try { + ;({ value: lamports } = await rpc + .getBalance(pubkey, { commitment: solana.commitment }) + .send()) + } catch (error) { + console.error("Failed to fetch balance:", error) + console.log("Will retry after next attempt...") + continue + } console.log("SOL balance fetched (lamports):", lamports) }Note: The same error handling could be added to the initial balance fetch on line 51-53.
demos/payments/src/index.ts (2)
484-487: Add emptyaccountsarray to memo instruction.Some instruction builders expect
accounts. Make it explicit to avoid encoding issues.- const memoInstruction = { - programAddress: address("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), - data: new TextEncoder().encode(expectedMemo) - } + const memoInstruction = { + programAddress: address("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), + accounts: [], + data: new TextEncoder().encode(expectedMemo) + }
571-573: AvoidskipPreflight: truein production flows.Preflight catches account/mint/ATA mistakes early. Consider turning it off by default or gating via env.
- .sendTransaction(wireTx, { encoding: "base64", skipPreflight: true }) + .sendTransaction(wireTx, { encoding: "base64", skipPreflight: false })Or:
- skipPreflight: true + skipPreflight: process.env.SOLANA_SKIP_PREFLIGHT === "1"demos/payments/src/receipt-service.ts (1)
276-285: Make tx polling a bit more robust.RPC availability can lag; consider increasing attempts or adding backoff.
- const maxAttempts = 10 - const delayMs = 1000 + const maxAttempts = 20 + const delayMs = 1500Optionally exponential backoff and early exit on
confirmationStatus === "confirmed".
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
demos/payments/README.md(2 hunks)demos/payments/package.json(1 hunks)demos/payments/src/constants.ts(1 hunks)demos/payments/src/index.ts(6 hunks)demos/payments/src/receipt-service.ts(5 hunks)demos/payments/src/server.ts(4 hunks)demos/payments/src/utils/ensure-balances.ts(2 hunks)demos/payments/src/utils/ensure-private-keys.ts(2 hunks)docs/ack-pay/payment-request-payload.mdx(2 hunks)docs/ack-pay/receipt-verification.mdx(1 hunks)docs/ack-pay/summary.mdx(1 hunks)docs/demos/demo-payments.mdx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
demos/payments/src/server.ts (2)
demos/payments/src/utils/ensure-private-keys.ts (1)
ensureSolanaKeys(19-59)demos/payments/src/constants.ts (1)
RECEIPT_SERVICE_URL(10-10)
demos/payments/src/utils/ensure-private-keys.ts (2)
tools/cli-tools/src/prompts.ts (1)
log(35-54)demos/payments/src/constants.ts (1)
envFilePath(17-17)
demos/payments/src/constants.ts (1)
packages/caip/src/caips/caip-2.ts (1)
caip2ChainIds(30-39)
demos/payments/src/utils/ensure-balances.ts (2)
demos/payments/src/constants.ts (1)
solana(31-42)tools/cli-tools/src/prompts.ts (1)
waitForEnter(8-14)
demos/payments/src/receipt-service.ts (2)
demos/payments/src/constants.ts (1)
solana(31-42)packages/ack-pay/src/schemas/valibot.ts (1)
paymentOptionSchema(7-16)
demos/payments/src/index.ts (8)
demos/payments/src/utils/keypair-info.ts (1)
KeypairInfo(15-24)tools/cli-tools/src/prompts.ts (2)
log(35-54)waitForEnter(8-14)demos/payments/src/constants.ts (1)
solana(31-42)demos/payments/src/utils/ensure-private-keys.ts (1)
ensureSolanaKeys(19-59)demos/payments/src/utils/ensure-balances.ts (1)
ensureSolanaSolBalance(48-73)packages/jwt/src/signer.ts (1)
createJwtSigner(17-28)packages/did/src/methods/did-pkh.ts (1)
createDidPkhUri(138-143)packages/jwt/src/create-jwt.ts (1)
createJwt(29-44)
🔇 Additional comments (11)
docs/ack-pay/summary.mdx (1)
8-8: LGTM!The documentation accurately reflects the new Solana on-chain payment support added in this PR. The explanation is clear and correctly positions on-chain examples (EVM and Solana) alongside traditional finance and card network options.
demos/payments/README.md (1)
9-9: LGTM!The documentation accurately reflects the expanded payment options now available in the demo, including the new Solana devnet path. The updates are clear and align with the implementation changes in this PR.
Also applies to: 54-55
docs/ack-pay/payment-request-payload.mdx (1)
49-59: LGTM!The new Solana payment option example is well-documented with appropriate CAIP-2 chain ID format for Solana devnet. The explanatory note about Solana-specific addressing (CAIP-2 chain IDs and base58 wallet addresses) is helpful for implementers.
Also applies to: 102-102
docs/ack-pay/receipt-verification.mdx (1)
107-118: LGTM!The on-chain verification steps for Solana are well-documented and provide clear guidance on how memo-based binding, mint validation, recipient verification, and balance delta checks should be performed. This complements the general VC verification process effectively.
demos/payments/src/server.ts (2)
51-56: LGTM!The Solana key initialization is correct. The keys are properly ensured at request time and the public key is extracted for use as the recipient address in the payment option.
99-109: LGTM!The Solana payment option is properly configured with the correct CAIP-2 chain ID for devnet, appropriate amount/decimals for USDC, and uses the server's Solana public key as the recipient. The configuration aligns with the documented payment request payload format.
docs/demos/demo-payments.mdx (1)
69-73: LGTM!The documentation clearly describes the Solana devnet payment path, including the important detail about memo-based binding for replay protection. This helps users understand how the Solana flow differs from the Base Sepolia path.
demos/payments/src/utils/ensure-balances.ts (1)
48-73: Otherwise, LGTM!The balance-checking implementation follows the same pattern as the existing EVM balance check (
ensureNonZeroBalances) and provides helpful faucet URLs with a prefilled link for user convenience. The commitment level is correctly pulled from the Solana configuration.demos/payments/package.json (1)
32-36: Confirm latest package versions: both @solana-program/system@^0.9.0 and @solana-program/token@^0.6.0 are the current stable releases with no reported vulnerabilities.demos/payments/src/utils/ensure-private-keys.ts (2)
29-33: Ensure WebCrypto Ed25519 is available in your Node runtime.
crypto.subtle.generateKey("Ed25519", ...)requires modern Node (WebCrypto enabled). If portability is a concern, import explicitly.Apply this optional import to avoid reliance on globals:
+import { webcrypto as crypto } from "node:crypto"Run
node -vand ensure CI uses a Node version supporting Ed25519 WebCrypto.
41-44: Use raw Ed25519 export and pass bytes to getAddressFromPublicKey
- Replace
exportKey("spki")+.slice(-32)withawait crypto.subtle.exportKey("raw", kp.publicKey)andnew Uint8Array(...).- Confirm
getAddressFromPublicKeytakes aUint8Arrayand is synchronous (removeawaitand passpublicKeyBytes).
- Verify Issues key to be a signer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
demos/payments/src/server.ts (1)
41-46: Cache Solana keys at startup instead of per-request.Calling
ensureSolanaKeyson every request is inefficient. If the keys exist inprocess.env, this repeatedly reads environment variables; if they don't exist, key generation blocks the request.Move this call to module initialization (outside the request handler) to compute it once at startup:
+const solanaKeys = await ensureSolanaKeys( + "SOLANA_SERVER_PUBLIC_KEY", + "SOLANA_SERVER_SECRET_KEY_JSON" +) +const solanaServerPublicKey = solanaKeys.publicKey + const app = new Hono<Env>()Then reference
solanaServerPublicKeydirectly in the request handler:app.get("/", async (c): Promise<TypedResponse<{ message: string }>> => { const serverIdentity = await getKeypairInfo(env(c).SERVER_PRIVATE_KEY_HEX) const didResolver = getDidResolver() - // Ensure Solana server keys are present - const solanaKeys = await ensureSolanaKeys( - "SOLANA_SERVER_PUBLIC_KEY", - "SOLANA_SERVER_SECRET_KEY_JSON" - ) - const solanaServerPublicKey: string = solanaKeys.publicKey - const { did: receiptIssuerDid } = await getKeypairInfo(demos/payments/src/index.ts (1)
463-471: Validate secret key format.The code assumes
clientSolKeys.secretKeyJsonis a valid JSON array representing a 64-byte Solana keypair, but doesn't validate the format or length before passing it tocreateKeyPairSignerFromBytes.Add validation to ensure the key is 64 bytes:
const keyBytes = new Uint8Array( JSON.parse(clientSolKeys.secretKeyJson) as number[] ) + if (keyBytes.length !== 64) { + throw new Error("Invalid Solana secret key length (expected 64 bytes)") + } const payerSigner = await createKeyPairSignerFromBytes(keyBytes)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
demos/payments/src/index.ts(6 hunks)demos/payments/src/receipt-service.ts(5 hunks)demos/payments/src/server.ts(4 hunks)demos/payments/src/utils/ensure-balances.ts(2 hunks)demos/payments/src/utils/ensure-private-keys.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- demos/payments/src/utils/ensure-private-keys.ts
🧰 Additional context used
🧬 Code graph analysis (4)
demos/payments/src/utils/ensure-balances.ts (2)
demos/payments/src/constants.ts (1)
solana(31-42)tools/cli-tools/src/prompts.ts (1)
waitForEnter(8-14)
demos/payments/src/server.ts (2)
demos/payments/src/utils/ensure-private-keys.ts (1)
ensureSolanaKeys(19-59)demos/payments/src/constants.ts (1)
RECEIPT_SERVICE_URL(10-10)
demos/payments/src/receipt-service.ts (3)
demos/payments/src/constants.ts (1)
solana(31-42)packages/ack-pay/src/schemas/valibot.ts (1)
paymentOptionSchema(7-16)packages/did/src/methods/did-pkh.ts (1)
addressFromDidPkhUri(95-100)
demos/payments/src/index.ts (7)
demos/payments/src/utils/keypair-info.ts (1)
KeypairInfo(15-24)demos/payments/src/constants.ts (1)
solana(31-42)demos/payments/src/utils/ensure-private-keys.ts (1)
ensureSolanaKeys(19-59)demos/payments/src/utils/ensure-balances.ts (1)
ensureSolanaSolBalance(49-86)packages/jwt/src/signer.ts (1)
createJwtSigner(17-28)packages/did/src/methods/did-pkh.ts (1)
createDidPkhUri(138-143)packages/jwt/src/create-jwt.ts (1)
createJwt(29-44)
🔇 Additional comments (15)
demos/payments/src/server.ts (2)
14-14: LGTM!The import follows the existing pattern and is placed appropriately.
89-99: LGTM!The Solana payment option is properly structured and follows the same pattern as the existing Base Sepolia and Stripe options. The network identifier, amount, decimals, and recipient are all correctly configured for Solana devnet.
demos/payments/src/utils/ensure-balances.ts (2)
1-6: LGTM!The imports are necessary for the Solana balance checking functionality and follow the existing patterns in the file.
49-86: LGTM!The function mirrors the pattern established by
ensureNonZeroBalancesand includes appropriate error handling with retry logic. The user experience is clear, providing faucet URLs and a prefilled link for convenience.demos/payments/src/receipt-service.ts (6)
1-37: LGTM!The imports are necessary for Solana transaction verification and follow best practices by using narrowed type imports to minimize dependencies.
121-128: LGTM!The routing logic correctly dispatches to the Solana verification flow when the network matches the Solana devnet chain ID, with appropriate error handling for unsupported networks.
246-258: LGTM!The helper function and type definitions are well-structured. Using
jsonParsedencoding and the configured commitment level from constants is appropriate for transaction verification.
288-317: LGTM! Signer verification now implemented.The signer verification correctly ensures the JWT issuer DID matches a transaction signer, addressing the critical security concern raised in the previous review. The implementation properly handles both string and object pubkey formats from the jsonParsed transaction.
376-424: LGTM!The memo extraction function comprehensively handles both parsed and partially-decoded memo formats, including inner instructions. The error handling for malformed base58 data is appropriate.
331-373: Verify mint address matches expected USDC mint.The token balance verification checks the mint from the transaction but doesn't validate that it matches
solana.usdcMintfrom constants. An attacker could potentially send a different SPL token with the same amount and decimals.Add mint validation before the balance checks:
// Validate postTokenBalances reflect the transfer to recipient for the mint const mint = solana.usdcMint + const recipient = typeof paymentOption.recipient === "string" ? paymentOption.recipient : String(paymentOption.recipient) const dec = paymentOption.decimals const expectedAmount = BigInt(paymentOption.amount) type TokenBalance = { mint: string owner: string uiTokenAmount: { amount: string; decimals: number } } const post = (tx.meta?.postTokenBalances ?? []) as unknown as TokenBalance[] const pre = (tx.meta?.preTokenBalances ?? []) as unknown as TokenBalance[] const preBal = pre.find((b) => b.mint === mint && b.owner === recipient) const postBal = post.find((b) => b.mint === mint && b.owner === recipient) if (!postBal) { log(errorMessage("Recipient post token balance not found")) throw new HTTPException(400, { message: "Recipient not credited" }) } + if (postBal.mint !== mint) { + log(errorMessage("Invalid mint address")) + throw new HTTPException(400, { message: "Invalid mint" }) + } if (postBal.uiTokenAmount.decimals !== dec) { log(errorMessage("Invalid token decimals")) throw new HTTPException(400, { message: "Invalid token decimals" }) }Likely an incorrect or invalid review comment.
demos/payments/src/index.ts (5)
1-63: LGTM!The imports are necessary for implementing the Solana payment flow, including transaction construction, signing, and SPL token transfers.
182-196: LGTM!The UI updates correctly identify Solana-based payment options by checking if the network starts with
"solana:", which aligns with the CAIP-2 chain ID format.
218-239: LGTM!The routing logic correctly dispatches to the Solana payment flow when the network is a string starting with
"solana:", with appropriate fallback to other payment methods and error handling.
501-580: LGTM!The transaction construction is comprehensive and correct:
- Ensures the sender has USDC balance (prompting Circle faucet if needed)
- Creates recipient ATA if it doesn't exist
- Properly constructs the transaction with memo, optional ATA creation, and transfer instructions
- Uses the pipe pattern for clean transaction building
The memo placement first in the instruction list is good practice.
582-617: LGTM!The receipt signing flow correctly derives an ACK-compatible Ed25519 keypair from the Solana keypair seed, ensuring cryptographic binding between the Solana transaction signer and the ACK JWT issuer. The
payerDidis properly constructed as a did:pkh URI with the Solana chain ID and public key.
- I think the Memo is probably not necessary when we check the JWT signature in the receipt. Will make it easier for agents to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
demos/payments/src/index.ts(5 hunks)demos/payments/src/receipt-service.ts(4 hunks)docs/ack-pay/receipt-verification.mdx(1 hunks)docs/demos/demo-payments.mdx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
demos/payments/src/receipt-service.ts (3)
demos/payments/src/constants.ts (1)
solana(31-42)packages/ack-pay/src/schemas/valibot.ts (1)
paymentOptionSchema(7-16)packages/did/src/methods/did-pkh.ts (1)
addressFromDidPkhUri(95-100)
demos/payments/src/index.ts (8)
demos/payments/src/utils/keypair-info.ts (1)
KeypairInfo(15-24)tools/cli-tools/src/prompts.ts (2)
log(35-54)waitForEnter(8-14)demos/payments/src/constants.ts (1)
solana(31-42)demos/payments/src/utils/ensure-private-keys.ts (1)
ensureSolanaKeys(19-59)demos/payments/src/utils/ensure-balances.ts (1)
ensureSolanaSolBalance(49-86)packages/jwt/src/signer.ts (1)
createJwtSigner(17-28)packages/did/src/methods/did-pkh.ts (1)
createDidPkhUri(138-143)packages/jwt/src/create-jwt.ts (1)
createJwt(29-44)
🪛 LanguageTool
docs/demos/demo-payments.mdx
[grammar] ~70-~70: There might be a mistake here.
Context: ...ctly to the Server’s Solana address. The Receipt Service verifies the mint, recip...
(QB_NEW_EN)
[grammar] ~71-~71: There might be a mistake here.
Context: ...ecipient and exact amount before issuing a receipt. <Step title="Receip...
(QB_NEW_EN)
docs/ack-pay/receipt-verification.mdx
[grammar] ~113-~113: There might be a mistake here.
Context: ...iated token account for recipient+mint). - Compute the delta in recipient `postToke...
(QB_NEW_EN)
🔇 Additional comments (15)
docs/demos/demo-payments.mdx (1)
68-73: LGTM! Clear documentation of the Solana payment flow.The added documentation clearly describes the Solana devnet payment path, including the transfer of SPL-USDC and verification by the Receipt Service.
docs/ack-pay/receipt-verification.mdx (1)
106-117: LGTM! Comprehensive Solana verification documentation.The new section provides clear guidance on verifying Solana on-chain payments, covering all essential verification steps including transaction fetching, mint/recipient validation, balance delta computation, and idempotency checks.
demos/payments/src/receipt-service.ts (4)
10-10: LGTM! Appropriate imports for Solana verification.The imports are minimal and well-structured, with type-only imports used where appropriate to minimize dependencies.
Also applies to: 12-12, 28-28, 31-31, 33-33
118-119: LGTM! Consistent routing for Solana payments.The routing logic follows the same pattern as other payment methods and correctly dispatches to the Solana verification function.
243-251: LGTM! Clean transaction fetching helper.The helper function appropriately configures the transaction fetch with the correct commitment level and encoding for verification needs.
255-358: LGTM! Comprehensive Solana payment verification.The verification function properly implements all necessary checks:
- Network validation (lines 260-263): Ensures correct chain
- Transaction polling (lines 269-281): Includes retry logic with appropriate delays
- Signer verification (lines 283-312): Correctly binds JWT issuer DID to transaction signer, addressing the previous review concern
- Token balance verification (lines 314-357): Validates mint, recipient, decimals, and amount delta
The implementation includes appropriate error handling and type safety measures throughout.
demos/payments/src/index.ts (9)
15-32: LGTM! Well-organized Solana imports.The imports are properly grouped by library and include all necessary utilities for constructing, signing, and sending Solana transactions.
33-43: LGTM! Necessary imports for Solana payment flow.The additional imports provide the required utilities for DID creation, JWT signing, and wallet management for the Solana payment path.
Also applies to: 55-55, 59-62
181-195: LGTM! Clear payment option UI for Solana.The updated UI logic correctly identifies Solana networks using the CAIP-2 format and provides clear, user-friendly labels and descriptions.
217-227: LGTM! Consistent routing for Solana payments.The routing logic properly type-checks the network field and calls the Solana payment handler when appropriate.
450-475: LGTM! Proper initialization and key setup.The function correctly initializes the Solana RPC connection, loads client keys, and creates the payer signer. The SOL balance check ensures the client has funds for transaction fees.
479-488: LGTM! Correct ATA derivation.The function properly derives the associated token accounts (ATAs) for both the sender and recipient using the standard token program.
490-530: LGTM! Robust balance checking and ATA creation.The implementation includes:
- Balance verification: Checks sender USDC balance with proper error handling
- User guidance: Prompts for Circle faucet if balance is zero
- Conditional ATA creation: Creates recipient ATA only if it doesn't exist, avoiding unnecessary transactions
532-567: LGTM! Well-constructed Solana transaction.The transaction construction properly:
- Creates the transfer instruction with checked amounts and decimals
- Uses the pipe pattern for clean transaction message building
- Sets appropriate lifetime using latest blockhash
- Signs with the payer signer
- Includes optional ATA creation when needed
The transaction flow is clean and follows Solana best practices.
572-593: Ed25519 seed extraction validated. The first 32 bytes ofsecretKey64correspond to the JWKdprivate scalar fromensureSolanaKeys.
|
hey @venables What do you think about the PR? I would like to produce some video content about the different x402 options . Would be great to have this merged. I built some examples using ACK here: https://solana-paywal.vercel.app/ and a twitter bot that can pay using ACK to animate images: https://x.com/solanadevhelper/status/1978483896598544449 |
venables
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for putting this together @Woody4618 . I think it's getting close.
| const pubkey = solAddress(address) | ||
| let lamports = 0n | ||
| try { | ||
| ;({ value: lamports } = await rpc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is pretty hard to read -- but these kinds of lines in particular are a challenge. Please prefer to load into a variable then set the lamports var from there, e.g.
const balance = await rpc...
lamports = balance.value| console.log("Faucet: https://faucet.solana.com/") | ||
| const prefilled = `https://faucet.solana.com/?walletAddress=${encodeURIComponent( | ||
| address | ||
| )}&amount=0.5` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an airdrop method to handle this for us: https://www.solanakit.com/api/type-aliases/RequestAirdropApi
| "@solana/keys": "^4.0.0", | ||
| "@solana/kit": "^4.0.0", | ||
| "agentcommercekit": "workspace:*", | ||
| "bs58": "^6.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed?
| "@solana-program/system": "^0.9.0", | ||
| "@solana-program/token": "^0.6.0", | ||
| "@solana/addresses": "^4.0.0", | ||
| "@solana/keys": "^4.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these strictly necessary if we install @solana/kit?
| import { getKeypairInfo } from "./utils/keypair-info" | ||
| import type { Rpc, Signature } from "@solana/kit" | ||
| import type { paymentOptionSchema } from "agentcommercekit/schemas/valibot" | ||
| import type { GetTransactionApi } from "gill" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is re-exported via solana/kit as well and we can skip the gill import/package.json entry
| @@ -1,6 +1,9 @@ | |||
| // cspell:ignore lamports | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can probably add this to the cspell.config.yaml file at the root
| export const solana = { | ||
| // CAIP-2 chain id for Solana devnet | ||
| chainId: caip2ChainIds.solanaDevnet, | ||
| // Example RPC; users can override via environment if desired |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for these comments
| ? "Stripe" | ||
| : option.network?.startsWith("solana:") | ||
| ? "Solana" | ||
| : "Base Sepolia", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be ideal to avoid nested ternary oeprators
| receipt = paymentResult.receipt | ||
| details = paymentResult.details | ||
| } else if ( | ||
| typeof selectedPaymentOption.network === "string" && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needed?
| const kp = await crypto.subtle.generateKey("Ed25519", true, [ | ||
| "sign", | ||
| "verify" | ||
| ]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to use the key generation methods already exposed via ACK?
I believe this whole method could be greatly simplified by using those.
@@ -25,32 +25,18 @@
if (existingPub && existingSecret) {
return { publicKey: existingPub, secretKeyJson: existingSecret }
}
log(colors.dim(`Generating ${pubEnv}/${secretEnv}...`))
- const kp = await crypto.subtle.generateKey("Ed25519", true, [
- "sign",
- "verify"
- ])
- const privateKeyJwk = await crypto.subtle.exportKey("jwk", kp.privateKey)
- const privateKeyBase64 = privateKeyJwk.d
- if (!privateKeyBase64) throw new Error("Failed to get private key bytes")
+ const kp = await generateKeypair("Ed25519")
- const privateKeyBytes = new Uint8Array(
- Buffer.from(privateKeyBase64, "base64url")
- )
- // Export raw 32-byte public key from SPKI (last 32 bytes of the DER-encoded key)
- const publicKeySpki = await crypto.subtle.exportKey("spki", kp.publicKey)
- const publicKeyBytes = new Uint8Array(publicKeySpki).slice(-32)
// Concatenate 32-byte private key + 32-byte public key => 64-byte secret key
- const secretKey64 = new Uint8Array(
- privateKeyBytes.length + publicKeyBytes.length
- )
- secretKey64.set(privateKeyBytes, 0)
- secretKey64.set(publicKeyBytes, privateKeyBytes.length)
+ const secretKey64 = new Uint8Array(kp.privateKey.length + kp.publicKey.length)
+ secretKey64.set(kp.privateKey, 0)
+ secretKey64.set(kp.publicKey, kp.privateKey.length)
const secretKeyJson = JSON.stringify(Array.from(secretKey64))
- const publicKey = await getAddressFromPublicKey(kp.publicKey)
+ const publicKey = bytesToBase58(kp.publicKey)
I added an e2e example using Solana Devnet and USDC mint.
I also added a memo instruction to prevent replay attacks that is vcalidated in the receipts service.
Here is an example payment:
https://explorer.solana.com/tx/4y19PDDTBnFCRGtmcSK9egmx6mYMZ1YD8ijJ9vCKZz3uWSzuCnweeo6Fc12hJ8o9T75ANof55JYVKRyNuevoxHz5?cluster=devnet
Here the successful demo succeeding locally:

Summary by CodeRabbit
New Features
Documentation