Skip to content
Merged
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
1 change: 0 additions & 1 deletion apps/app/hooks/htlc/useHTLCWriteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useCallback } from 'react'
import { useConfig } from 'wagmi'
import { getWalletClient } from 'wagmi/actions'
import { createHTLCClient as createClient, IHTLCClient } from '@train-protocol/sdk'
import '@train-protocol/sdk-evm'
import type { EvmSigner } from '@train-protocol/sdk-evm'
import { Network } from '../../Models/Network'
import { Wallet } from '@/Models/WalletProvider'
Expand Down
1 change: 0 additions & 1 deletion apps/app/lib/htlc/createHTLCClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createHTLCClient as createClient, IHTLCClient } from '@train-protocol/sdk'
import '@train-protocol/sdk-evm' // side-effect: registers eip155 htlc client
import { Network } from '../../Models/Network'

export function createHTLCClient(
Expand Down
1 change: 0 additions & 1 deletion apps/app/lib/htlc/secretDerivation/walletSign/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import { getAccount } from '@wagmi/core'
import { Config } from 'wagmi'
import { deriveKeyFromWallet } from '@train-protocol/sdk'
import '@train-protocol/sdk-evm' // side-effect: registers evm wallet sign
import { getEvmTypedData as sdkGetEvmTypedData } from '@train-protocol/sdk-evm'

const isSandbox = process.env.NEXT_PUBLIC_API_VERSION === 'sandbox'
Expand Down
5 changes: 5 additions & 0 deletions apps/app/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { useEffect } from "react";
import { PostHogProvider } from 'posthog-js/react'
import posthog from 'posthog-js'
import { Analytics } from '@vercel/analytics/next';
import { registerEvmSdk } from '@train-protocol/sdk-evm';

if (typeof window !== 'undefined') {
registerEvmSdk();
}

const progress = new ProgressBar({
size: 2,
Expand Down
29 changes: 29 additions & 0 deletions packages/sdk-evm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# @train-protocol/sdk-evm

EVM (eip155) HTLC client and wallet-sign provider for the Train Protocol SDK.

## Setup

Call `registerEvmSdk()` once at app startup **before** using any EVM HTLC or wallet-sign features:

```ts
import { registerEvmSdk } from '@train-protocol/sdk-evm'

registerEvmSdk() // idempotent — safe to call more than once
```

This registers:

- **HTLC client factory** for the `eip155` chain namespace (`createHTLCClient('eip155', ...)`)
- **Wallet-sign factory** for EVM wallets (`deriveKeyFromWallet('eip155', ...)`)

## Exports

| Export | Description |
| --------------------------- | ---------------------------------------------- |
| `registerEvmSdk()` | Register EVM providers (call once at startup) |
| `EvmHTLCClient` | EVM HTLC client class |
| `deriveKeyFromEvmSignature` | Derive key from EVM wallet signature |
| `getEvmTypedData` | Get EIP-712 typed data for signing |
| `EvmSigner` (type) | Signer interface for write operations |
| `Eip1193Provider` (type) | EIP-1193 provider interface |
10 changes: 5 additions & 5 deletions packages/sdk-evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
"default": "./dist/esm/index.js"
}
},
"sideEffects": [
"./dist/esm/index.js"
],
"sideEffects": false,
"files": [
"dist",
"dist/**/*"
Expand All @@ -23,7 +21,8 @@
"build:esm+types": "tsc --project tsconfig.json --rootDir ./src --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types",
"clean": "rimraf dist tsconfig.tsbuildinfo",
"dev": "tsc --project tsconfig.json --rootDir ./src --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types --watch",
"check:types": "tsc --noEmit"
"check:types": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"ox": "^0.13.2"
Expand All @@ -35,7 +34,8 @@
"@train-protocol/sdk": "workspace:^",
"@types/node": "^20",
"rimraf": "^6.0.1",
"typescript": "catalog:"
"typescript": "catalog:",
"vitest": "^4.0.18"
},
"engines": {
"node": ">=18"
Expand Down
59 changes: 59 additions & 0 deletions packages/sdk-evm/src/__tests__/registerEvmSdk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, it, expect, beforeEach } from 'vitest'
import {
getRegisteredNamespaces,
getRegisteredWalletSignProviders,
createHTLCClient,
deriveKeyFromWallet,
} from '@train-protocol/sdk'
import { registerEvmSdk } from '../index.js'

describe('registerEvmSdk', () => {
// Note: because the registry is a global singleton and registerEvmSdk is
// idempotent (guarded by a module-level flag), these tests run in sequence
// and the first call registers while subsequent calls are no-ops.

beforeEach(() => {
registerEvmSdk()
})

it('registers the eip155 HTLC client', () => {
expect(getRegisteredNamespaces()).toContain('eip155')
})

it('registers the eip155 wallet-sign provider', () => {
expect(getRegisteredWalletSignProviders()).toContain('eip155')
})

it('createHTLCClient works for eip155 after registration', () => {
const client = createHTLCClient('eip155', { rpcUrl: 'https://example.com' })
expect(client).toBeDefined()
expect(typeof client.getUserLockDetails).toBe('function')
expect(typeof client.getSolverLockDetails).toBe('function')
})

it('deriveKeyFromWallet is callable for eip155 after registration', async () => {
// We can't fully exercise wallet signing without a real EIP-1193 provider,
// but we verify the factory is wired up (it rejects with a provider error,
// not a "no wallet sign registered" error).
await expect(
deriveKeyFromWallet('eip155', { provider: null, address: '0x0' })
).rejects.toThrow()

// Confirm the error is NOT "No wallet sign registered" — that would mean
// registration didn't work. Any other error means the factory was found.
try {
await deriveKeyFromWallet('eip155', { provider: null, address: '0x0' })
} catch (e: unknown) {
expect((e as Error).message).not.toContain('No wallet sign registered')
}
})

it('multiple calls do not throw or double-register', () => {
// Call again — should be a no-op
expect(() => registerEvmSdk()).not.toThrow()

// Still only one eip155 entry (Map.set overwrites, but the guard prevents even that)
const namespaces = getRegisteredNamespaces().filter(ns => ns === 'eip155')
expect(namespaces).toHaveLength(1)
})
})
37 changes: 23 additions & 14 deletions packages/sdk-evm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@ import { deriveKeyFromEvmSignature } from './login/index.js'
import type { Eip1193Provider } from './login/index.js'
import type { EvmSigner } from './types.js'

// Self-register EVM HTLC client for the eip155 namespace
registerHTLCClient('eip155', (config) => new EvmHTLCClient({
rpcUrl: config.rpcUrl as string,
signer: config.signer as EvmSigner | undefined,
chainId: config.chainId as number | undefined,
}))
let registered = false

// Self-register EVM wallet sign for login
registerWalletSign('eip155', async (config) => {
return deriveKeyFromEvmSignature(
config.provider as Eip1193Provider,
config.address as `0x${string}`,
config.options as { sandbox?: boolean; currentChainId?: number } | undefined,
)
})
/**
* Explicitly register the EVM HTLC client and wallet-sign factories.
* Call once at app startup. Safe to call multiple times (idempotent).
*/
export function registerEvmSdk(): void {
if (registered) return
registered = true

registerHTLCClient('eip155', (config) => new EvmHTLCClient({
rpcUrl: config.rpcUrl as string,
signer: config.signer as EvmSigner | undefined,
chainId: config.chainId as number | undefined,
}))

registerWalletSign('eip155', async (config) => {
return deriveKeyFromEvmSignature(
config.provider as Eip1193Provider,
config.address as `0x${string}`,
config.options as { sandbox?: boolean; currentChainId?: number } | undefined,
)
})
}

export { EvmHTLCClient } from './client.js'
export type { EvmHTLCClientConfig, EvmSigner } from './types.js'
Expand Down
7 changes: 7 additions & 0 deletions packages/sdk-evm/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
include: ['src/__tests__/**/*.test.ts'],
},
})
4 changes: 2 additions & 2 deletions packages/sdk/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function createHTLCClient(chainNamespace: string, config: Record<string,
if (!factory) {
throw new Error(
`No HTLC client registered for chain namespace: ${chainNamespace}. ` +
`Did you forget to import the corresponding @train-protocol/sdk-* package?`
`Did you forget to call the corresponding register function (e.g. registerEvmSdk())?`
)
}
return factory(config)
Expand All @@ -38,7 +38,7 @@ export function deriveKeyFromWallet(providerName: string, config: Record<string,
if (!factory) {
throw new Error(
`No wallet sign registered for provider: ${providerName}. ` +
`Did you forget to import the corresponding @train-protocol/sdk-* package?`
`Did you forget to call the corresponding register function (e.g. registerEvmSdk())?`
)
}
return factory(config)
Expand Down
Loading