Skip to content

feat(sdk): add createKitKoraClient for plugin-based gasless transactions#388

Merged
amilz merged 4 commits intorelease/2.2.0from
feat/kit-paymaster-client
Mar 16, 2026
Merged

feat(sdk): add createKitKoraClient for plugin-based gasless transactions#388
amilz merged 4 commits intorelease/2.2.0from
feat/kit-paymaster-client

Conversation

@amilz
Copy link
Contributor

@amilz amilz commented Mar 13, 2026

Summary

  • Port createKitKoraClient from main to release/2.2.0, adapted for release SDK's reCAPTCHA, userId, optional fee_in_token, and existing plugin structure
  • Add typescript_free integration test phase with kit client free pricing tests
  • Add just test-ts command to run all TypeScript tests (unit + basic + auth + free) in one shot
  • Bump @solana/kit peer dep to ^6.1.0 for kit-plugin compatibility

New files

  • sdks/ts/src/kit/payment.ts — placeholder payment instruction builder/updater
  • sdks/ts/src/kit/planner.ts — compute budget + transaction planner
  • sdks/ts/src/kit/executor.ts — plan executor with optional fee_in_token handling + userId passthrough
  • sdks/ts/src/kit/index.tscreateKitKoraClient factory composing Kit plugins
  • sdks/ts/test/kit-client.test.ts — 19 unit tests including reCAPTCHA passthrough

Modified files

  • sdks/ts/package.json — kit-plugin peer/dev deps, test:integration:free script
  • sdks/ts/src/types/index.tsKoraKitClientConfig interface
  • sdks/ts/src/index.ts — export createKitKoraClient + KoraKitClient
  • sdks/ts/test/integration.test.tsFREE_PRICING guards, kit client free pricing tests
  • tests/src/test_runner/commands.rstypescript_free mapping
  • tests/src/test_runner/test_cases.toml[test.typescript_free] phase
  • tests/src/common/fixtures/kora-free-test.toml — add ComputeBudget to allowed programs
  • justfiletest-ts recipe

Test plan

  • pnpm run build — compiles clean
  • pnpm run lint — no errors
  • pnpm run test:unit — 37 unit tests pass
  • pnpm test kit-client.test.ts — 19 kit client tests pass
  • just test-ts — all 4 phases pass (unit, typescript_basic, typescript_auth, typescript_free)

Closes DEV-222


Open with Devin

📊 Unit Test Coverage

Coverage

Unit Test Coverage: 83.4%

View Detailed Coverage Report

amilz added 3 commits March 13, 2026 13:53
Port the Kit client from main to the release branch, adapting it to
the release SDK's reCAPTCHA, userId, and optional fee_in_token fields.
Also adds `just test-ts` to run all TypeScript tests in one command.
…nner

Port the typescript_free test phase from main: add ComputeBudget to
the free test config's allowed programs, wire up the test runner mapping,
add FREE_PRICING env guards to fee assertions, and add kit client free
pricing integration tests. Skip bundle tests in free pricing mode since
bundles are disabled in that config.
@amilz amilz self-assigned this Mar 13, 2026
@amilz amilz requested a review from dev-jodee March 13, 2026 21:54
@github-actions
Copy link

github-actions bot commented Mar 13, 2026

📊 TypeScript Coverage Report

Coverage: 38.8%

View detailed report

Coverage artifacts have been uploaded to this workflow run.
View Artifacts

devin-ai-integration[bot]

This comment was marked as resolved.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR ports createKitKoraClient from main to release/2.2.0, adding a plugin-based factory that composes @solana/kit plugins (RPC, payer, planner, executor) into a single client capable of gasless transactions with optional token-fee payment. It also introduces a typescript_free integration-test phase, a just test-ts recipe, and bumps the @solana/kit peer dep to ^6.1.0.

The overall design is sound and aligns well with the existing plugin architecture in the SDK. Key observations:

  • payment.ts — missing accounts null guard before unsafe cast: isPlaceholderPaymentInstruction guards ix.data via optional chaining but does not check ix.accounts before casting to NonNullable<...> and calling parseTransferInstruction. A malformed-but-matching instruction (correct program + discriminator, absent accounts) would throw inside map/filter, aborting transaction execution.
  • Dual KoraClient instances: createKitKoraClient constructs one KoraClient for bootstrap/executor use and a second one inside koraPlugin. The two share no state; any future per-client rate-limiting or retry counters would diverge silently.
  • Silent fee_in_token fallback: When the server omits fee_in_token in a paid-pricing context, the payment instruction is stripped with no warning, which could mask misconfiguration.
  • GetPayerSignerResponse.payment_address type: The field is typed as required string but is treated as potentially falsy in kit/index.ts, suggesting the server can return an empty value. The type should be string | null to surface this to all callers.

Confidence Score: 3/5

  • Safe to merge with minor fixes — no security regressions, but one logic issue in payment.ts could cause unexpected runtime errors in edge cases.
  • The core transaction flow is well-tested (19 unit tests + integration tests) and the architecture is coherent. The missing accounts null guard in isPlaceholderPaymentInstruction is a real defect that can propagate an unhandled exception during payment instruction replacement, though it requires a malformed-but-partially-matching instruction to trigger. The dual KoraClient issue and silent fee fallback are design concerns rather than breaking bugs. The type inaccuracy on payment_address is low-risk but should be corrected before wider adoption.
  • sdks/ts/src/kit/payment.ts (missing accounts null guard), sdks/ts/src/kit/index.ts (duplicate KoraClient), sdks/ts/src/types/index.ts (payment_address type)

Important Files Changed

Filename Overview
sdks/ts/src/kit/executor.ts Core executor logic — handles blockhash fetch, CU simulation, fee estimation, payment instruction update/removal, and sign+send. Two notable issues: fee_in_token silently defaulting to 0 could skip payment in edge cases, and lastValidBlockHeight is set to MAX_SAFE_INTEGER (intentional but unusual).
sdks/ts/src/kit/index.ts Factory composing Kit plugins into a Kora client — initialises two separate KoraClient instances (one for internal use, one via koraPlugin), which wastes resources and can diverge in auth/retry state.
sdks/ts/src/kit/payment.ts Placeholder/real payment instruction builder and matcher — isPlaceholderPaymentInstruction checks data[0] for null but skips an ix.accounts null guard before the unsafe parseTransferInstruction cast, which could throw on malformed instructions.
sdks/ts/src/kit/planner.ts Compute budget instruction builder and transaction planner — straightforward, correctly injects budget IXs and placeholder payment IX, toggles provisory CU limit as needed.
sdks/ts/src/types/index.ts Adds KoraKitClientConfig interface — well-typed with readonly fields, but GetPayerSignerResponse.payment_address is still typed as required string while kit/index.ts treats it as potentially falsy/empty.
sdks/ts/test/kit-client.test.ts 19 unit tests covering initialization, fee flows, reCAPTCHA passthrough, compute budget, and token2022 — good coverage; mock infrastructure is clean.
sdks/ts/test/integration.test.ts Adds Kit client free-pricing integration tests gated behind FREE_PRICING env var — correctly reuses testWallet and koraAddress, no issues.
tests/src/common/fixtures/kora-free-test.toml Adds ComputeBudget to allowed_programs — required for Kit client to submit CU budget instructions in free pricing tests.
justfile Adds test-ts recipe running unit + typescript_basic/auth/free — consistent with existing integration-test recipe patterns.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant F as createKitKoraClient
    participant K1 as KoraClient (internal)
    participant K2 as KoraClient (koraPlugin)
    participant SR as Solana RPC
    participant KS as Kora Server

    U->>F: createKitKoraClient(config)
    F->>K1: new KoraClient(endpoint)
    F->>K2: koraPlugin → new KoraClient(endpoint)
    K1->>KS: getPayerSigner()
    KS-->>K1: {signer_address, payment_address}
    F->>SR: estimateComputeUnitLimitFactory(rpc)
    F-->>U: KoraKitClient {payer, planner, executor, kora}

    U->>U: client.sendTransaction([ix])
    Note over U: transactionPlannerPlugin
    U->>U: createKoraTransactionPlanner
    Note over U: Injects CU budget IXs + placeholder payment IX (amount=0)

    Note over U: createKoraTransactionPlanExecutor
    U->>KS: getBlockhash()
    KS-->>U: {blockhash}
    U->>SR: simulateTransaction (CU estimation)
    SR-->>U: {unitsConsumed}
    U->>U: partiallySignTransactionMessageWithSigners (pre-payment)

    alt payment path (payment_address truthy)
        U->>K1: estimateTransactionFee(feeToken, prePaymentTx)
        K1->>KS: estimateTransactionFee
        KS-->>K1: {fee_in_token}
        alt fee_in_token > 0
            U->>U: updatePaymentInstructionAmount(amount=fee_in_token)
        else fee_in_token == 0 or undefined
            U->>U: removePaymentInstruction
        end
        U->>U: rebuild resolvedMsg with finalIxs
    end

    U->>K1: signAndSendTransaction(finalTx, userId)
    K1->>KS: signAndSendTransaction
    KS-->>K1: {signature, signed_transaction}
    K1-->>U: signature
Loading

Comments Outside Diff (1)

  1. sdks/ts/src/types/index.ts, line 222-227 (link)

    payment_address typed as required string but treated as potentially empty/null at runtime

    kit/index.ts line 60 treats payment_address as potentially falsy:

    const paymentAddr = payment_address ? address(payment_address) : undefined;

    This defensive guard implies the server can legitimately return an empty string or null for payment_address (e.g. in free-pricing mode). The current type definition, however, says payment_address: string (non-optional, non-nullable), which means TypeScript consumers have no signal that this field can be empty.

    Consider narrowing the type to reflect the actual contract:

    This would surface the null case to all callers and avoid silent bugs in code that uses payment_address directly without a truthy guard.

Last reviewed commit: 58a9b97

greptile-apps[bot]

This comment was marked as resolved.

@linear
Copy link

linear bot commented Mar 14, 2026

dev-jodee
dev-jodee previously approved these changes Mar 16, 2026
@amilz amilz merged commit 2213ddd into release/2.2.0 Mar 16, 2026
15 of 16 checks passed
@amilz amilz deleted the feat/kit-paymaster-client branch March 16, 2026 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants