Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/js-token-interface-v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
on:
push:
branches:
- main
pull_request:
branches:
- "*"
types:
- opened
- synchronize
- reopened
- ready_for_review

name: js-token-interface-v2

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
token-interface-js-v2:
name: token-interface-js-v2
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest

services:
redis:
image: redis:8.0.1
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
LIGHT_PROTOCOL_VERSION: V2
REDIS_URL: redis://localhost:6379
CI: true

steps:
- name: Checkout sources
uses: actions/checkout@v6
with:
submodules: true

- name: Setup and build
uses: ./.github/actions/setup-and-build
with:
skip-components: "redis,disk-cleanup,go"
cache-key: "js"

- name: Build token-interface with V2
run: |
cd js/token-interface
pnpm build:v2

- name: Build CLI
run: |
just cli build

- name: Run token-interface unit tests with V2
run: |
echo "Running token-interface unit tests with retry logic (max 2 attempts)..."
attempt=1
max_attempts=2
until just js test-token-interface-unit-v2; do
attempt=$((attempt + 1))
if [ $attempt -gt $max_attempts ]; then
echo "Tests failed after $max_attempts attempts"
exit 1
fi
echo "Attempt $attempt/$max_attempts failed, retrying..."
sleep 5
done
echo "Tests passed on attempt $attempt"

- name: Run token-interface e2e tests with V2
run: |
echo "Running token-interface e2e tests with retry logic (max 2 attempts)..."
attempt=1
max_attempts=2
until just js test-token-interface-e2e-v2; do
attempt=$((attempt + 1))
if [ $attempt -gt $max_attempts ]; then
echo "Tests failed after $max_attempts attempts"
exit 1
fi
echo "Attempt $attempt/$max_attempts failed, retrying..."
sleep 5
done
echo "Tests passed on attempt $attempt"

- name: Display prover logs on failure
if: failure()
run: |
echo "=== Displaying prover logs ==="
find . -path "*/test-ledger/*prover*.log" -type f -exec echo "=== Contents of {} ===" \; -exec cat {} \; -exec echo "=== End of {} ===" \; || echo "No prover logs found"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ api-docs/
# Third-party dependencies
/.local
/.vscode
.cursor/

**/.idea
**/*.iml
Expand Down
14 changes: 13 additions & 1 deletion js/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,34 @@ default:
build:
cd stateless.js && pnpm build
cd compressed-token && pnpm build
cd token-interface && pnpm build

test: test-stateless test-compressed-token
test: test-stateless test-compressed-token test-token-interface

test-stateless:
cd stateless.js && pnpm test

test-compressed-token:
cd compressed-token && pnpm test

test-token-interface:
cd token-interface && pnpm test

test-token-interface-unit-v2:
cd token-interface && pnpm test:unit:all

test-token-interface-e2e-v2:
cd token-interface && pnpm test:e2e:all

test-compressed-token-unit-v2:
cd compressed-token && pnpm test:unit:all:v2

lint:
cd stateless.js && pnpm lint
cd compressed-token && pnpm lint
cd token-interface && pnpm lint

format:
cd stateless.js && pnpm format
cd compressed-token && pnpm format
cd token-interface && pnpm format
109 changes: 109 additions & 0 deletions js/token-interface/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# `@lightprotocol/token-interface`

Payments-focused helpers for Light rent-free token flows.

Use this when you want SPL-style transfers with unified sender handling:
- sender side auto wraps/loads into light ATA
- recipient ATA can be light (default), SPL, or Token-2022 via `tokenProgram`

## RPC client (required)

All builders expect `createRpc()` from `@lightprotocol/stateless.js`.

```ts
import { createRpc } from '@lightprotocol/stateless.js';

// Add this to your client. It is a superset of web3.js Connection RPC plus Light APIs.
const rpc = createRpc();
// Optional: createRpc(clusterUrl)
```

## Canonical for Kit users

Use `createTransferInstructionPlan` from `/kit`.

```ts
import { createTransferInstructionPlan } from '@lightprotocol/token-interface/kit';

const transferPlan = await createTransferInstructionPlan({
rpc,
payer: payer.publicKey,
mint,
sourceOwner: sender.publicKey,
authority: sender.publicKey,
recipient: customer.publicKey,
// Optional destination program:
// tokenProgram: TOKEN_PROGRAM_ID
amount: 25n,
});
```

If you prefer Kit instruction arrays instead of plans:

```ts
import { buildTransferInstructions } from '@lightprotocol/token-interface/kit';
```

## Canonical for web3.js users

Use `buildTransferInstructions` from the root export.

```ts
import { buildTransferInstructions } from '@lightprotocol/token-interface';

const instructions = await buildTransferInstructions({
rpc,
payer: payer.publicKey,
mint,
sourceOwner: sender.publicKey,
authority: sender.publicKey,
recipient: customer.publicKey,
amount: 25n,
});

// add memo if needed, then build/sign/send transaction
```

Backwards-compatible alias:

```ts
import { createTransferInstructions } from '@lightprotocol/token-interface';
```

## Raw single-instruction helpers

Use these when you want manual orchestration:

```ts
import {
createAtaInstruction,
createLoadInstruction,
createTransferCheckedInstruction,
} from '@lightprotocol/token-interface/instructions';
```

## No-wrap instruction-flow builders (advanced)

If you explicitly want to disable automatic sender wrapping, use:

```ts
import { buildTransferInstructionsNowrap } from '@lightprotocol/token-interface/instructions';
```

## Read account

```ts
import { getAta } from '@lightprotocol/token-interface';

const account = await getAta({ rpc, owner: customer.publicKey, mint });
console.log(account.amount, account.hotAmount, account.compressedAmount);
```

## Important rules

- Only one compressed sender account is loaded per call; smaller ones are ignored for that call.
- Transfer always builds checked semantics.
- Canonical builders always use wrap-enabled sender setup (`buildTransferInstructions`, `createLoadInstructions`, `createApproveInstructions`, `createRevokeInstructions`).
- If sender SPL/T22 balances were wrapped by the flow, source SPL/T22 ATAs are closed afterward.
- Recipient ATA is derived from `(recipient, mint, tokenProgram)`; default is light token program.
- Recipient-side load is still intentionally disabled.
113 changes: 113 additions & 0 deletions js/token-interface/eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const js = require('@eslint/js');
const tseslint = require('@typescript-eslint/eslint-plugin');
const tsParser = require('@typescript-eslint/parser');

module.exports = [
{
ignores: [
'node_modules/**',
'dist/**',
'build/**',
'coverage/**',
'*.config.js',
'eslint.config.js',
'jest.config.js',
'rollup.config.js',
],
},
js.configs.recommended,
{
files: ['**/*.js', '**/*.cjs', '**/*.mjs'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: {
require: 'readonly',
module: 'readonly',
process: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
exports: 'readonly',
console: 'readonly',
Buffer: 'readonly',
},
},
},
{
files: [
'tests/**/*.ts',
'**/*.test.ts',
'**/*.spec.ts',
'vitest.config.ts',
],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
process: 'readonly',
console: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
Buffer: 'readonly',
describe: 'readonly',
it: 'readonly',
expect: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
jest: 'readonly',
test: 'readonly',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
...tseslint.configs.recommended.rules,
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-require-imports': 0,
'no-prototype-builtins': 0,
'no-undef': 0,
'no-unused-vars': 0,
},
},
{
files: ['src/**/*.ts', 'src/**/*.tsx'],
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json',
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
process: 'readonly',
console: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
Buffer: 'readonly',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
...tseslint.configs.recommended.rules,
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-require-imports': 0,
'no-prototype-builtins': 0,
'no-undef': 0,
'no-unused-vars': 0,
},
},
];
Loading
Loading