@brninpay/sdk 0.1.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/.turbo/turbo-build.log +4 -0
- package/LICENSE +203 -0
- package/README.md +5 -0
- package/dist/actor-client.d.ts +13 -0
- package/dist/actor-client.d.ts.map +1 -0
- package/dist/actor-client.js +44 -0
- package/dist/actor-client.js.map +1 -0
- package/dist/bundler/client.d.ts +20 -0
- package/dist/bundler/client.d.ts.map +1 -0
- package/dist/bundler/client.js +228 -0
- package/dist/bundler/client.js.map +1 -0
- package/dist/bundler/providers/alchemy.d.ts +3 -0
- package/dist/bundler/providers/alchemy.d.ts.map +1 -0
- package/dist/bundler/providers/alchemy.js +15 -0
- package/dist/bundler/providers/alchemy.js.map +1 -0
- package/dist/bundler/providers/pimlico.d.ts +3 -0
- package/dist/bundler/providers/pimlico.d.ts.map +1 -0
- package/dist/bundler/providers/pimlico.js +12 -0
- package/dist/bundler/providers/pimlico.js.map +1 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +352 -0
- package/dist/client.js.map +1 -0
- package/dist/conversion.d.ts +3 -0
- package/dist/conversion.d.ts.map +1 -0
- package/dist/conversion.js +27 -0
- package/dist/conversion.js.map +1 -0
- package/dist/erc4337/builder.d.ts +15 -0
- package/dist/erc4337/builder.d.ts.map +1 -0
- package/dist/erc4337/builder.js +144 -0
- package/dist/erc4337/builder.js.map +1 -0
- package/dist/erc4337/providers/kernel.d.ts +23 -0
- package/dist/erc4337/providers/kernel.d.ts.map +1 -0
- package/dist/erc4337/providers/kernel.js +120 -0
- package/dist/erc4337/providers/kernel.js.map +1 -0
- package/dist/erc4337/providers/safe.d.ts +23 -0
- package/dist/erc4337/providers/safe.d.ts.map +1 -0
- package/dist/erc4337/providers/safe.js +117 -0
- package/dist/erc4337/providers/safe.js.map +1 -0
- package/dist/erc4337/providers/types.d.ts +88 -0
- package/dist/erc4337/providers/types.d.ts.map +1 -0
- package/dist/erc4337/providers/types.js +35 -0
- package/dist/erc4337/providers/types.js.map +1 -0
- package/dist/erc4337/smart-wallet.d.ts +16 -0
- package/dist/erc4337/smart-wallet.d.ts.map +1 -0
- package/dist/erc4337/smart-wallet.js +86 -0
- package/dist/erc4337/smart-wallet.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/payment-intents.d.ts +99 -0
- package/dist/modules/payment-intents.d.ts.map +1 -0
- package/dist/modules/payment-intents.js +118 -0
- package/dist/modules/payment-intents.js.map +1 -0
- package/dist/modules/policies.d.ts +87 -0
- package/dist/modules/policies.d.ts.map +1 -0
- package/dist/modules/policies.js +61 -0
- package/dist/modules/policies.js.map +1 -0
- package/dist/paymaster/client.d.ts +11 -0
- package/dist/paymaster/client.d.ts.map +1 -0
- package/dist/paymaster/client.js +119 -0
- package/dist/paymaster/client.js.map +1 -0
- package/dist/runtimee.d.ts +4 -0
- package/dist/runtimee.d.ts.map +1 -0
- package/dist/runtimee.js +4 -0
- package/dist/runtimee.js.map +1 -0
- package/dist/types.d.ts +389 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -0
- package/src/actor-client.ts +57 -0
- package/src/bundler/client.ts +306 -0
- package/src/bundler/providers/alchemy.ts +21 -0
- package/src/bundler/providers/pimlico.ts +17 -0
- package/src/client.ts +472 -0
- package/src/conversion.ts +25 -0
- package/src/erc4337/builder.ts +181 -0
- package/src/erc4337/providers/kernel.ts +136 -0
- package/src/erc4337/providers/safe.ts +133 -0
- package/src/erc4337/providers/types.ts +73 -0
- package/src/erc4337/smart-wallet.ts +115 -0
- package/src/index.ts +60 -0
- package/src/modules/payment-intents.ts +156 -0
- package/src/modules/policies.ts +79 -0
- package/src/paymaster/client.ts +155 -0
- package/src/runtimee.ts +3 -0
- package/src/types.ts +469 -0
- package/test/architecture/architecture-invariants.test.ts +61 -0
- package/test/client.test.ts +180 -0
- package/test/conversion.test.ts +42 -0
- package/test/runtimee.test.ts +30 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from 'fs'
|
|
3
|
+
import { join, relative } from 'path'
|
|
4
|
+
|
|
5
|
+
const SRC = join(import.meta.dirname, '../../src')
|
|
6
|
+
|
|
7
|
+
const nodeBuiltins = [
|
|
8
|
+
'fs', 'path', 'crypto', 'http', 'https', 'os', 'child_process',
|
|
9
|
+
'stream', 'net', 'dns', 'buffer', 'events', 'assert', 'tls',
|
|
10
|
+
'cluster', 'dgram', 'readline', 'repl', 'vm', 'worker_threads',
|
|
11
|
+
'perf_hooks', 'async_hooks', 'v8', 'inspector', 'wasi',
|
|
12
|
+
'diagnostics_channel',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
function walk(dir: string): string[] {
|
|
16
|
+
const files: string[] = []
|
|
17
|
+
try {
|
|
18
|
+
for (const entry of readdirSync(dir)) {
|
|
19
|
+
const full = join(dir, entry)
|
|
20
|
+
if (statSync(full).isDirectory()) {
|
|
21
|
+
files.push(...walk(full))
|
|
22
|
+
} else if (entry.endsWith('.ts') && !entry.endsWith('.d.ts')) {
|
|
23
|
+
files.push(full)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch { /* skip */ }
|
|
27
|
+
return files
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseImports(source: string): string[] {
|
|
31
|
+
const imports: string[] = []
|
|
32
|
+
const re = /import(?: type)?\s+(?:\{[^}]*\}|[^'"]+)\s+from\s+['"]([^'"]+)['"]/g
|
|
33
|
+
let m
|
|
34
|
+
while ((m = re.exec(source)) !== null) imports.push(m[1])
|
|
35
|
+
return imports
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('@brninpay/sdk — architecture invariants', () => {
|
|
39
|
+
const files = walk(SRC)
|
|
40
|
+
const nodeViolations: { file: string; imp: string }[] = []
|
|
41
|
+
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const source = readFileSync(file, 'utf8')
|
|
44
|
+
const imports = parseImports(source)
|
|
45
|
+
const rel = relative(SRC, file)
|
|
46
|
+
|
|
47
|
+
for (const imp of imports) {
|
|
48
|
+
if (nodeBuiltins.includes(imp.split('/')[0])) {
|
|
49
|
+
nodeViolations.push({ file: rel, imp })
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
it('must not import Node.js builtins (fs, crypto, http, path, etc.)', () => {
|
|
55
|
+
expect(nodeViolations, fmt('Node builtins', nodeViolations)).toHaveLength(0)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
function fmt(label: string, v: { file: string; imp: string }[]): string {
|
|
60
|
+
return v.length ? `\n\n${label}:\n${v.map((x) => ` ${x.file} → ${x.imp}`).join('\n')}` : ''
|
|
61
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { ValidationError } from '@brninpay/core'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { BrninpayClient } from '../src/client.js'
|
|
4
|
+
|
|
5
|
+
function createJsonResponse(body: unknown, status = 200): Response {
|
|
6
|
+
return new Response(JSON.stringify(body), {
|
|
7
|
+
status,
|
|
8
|
+
headers: {
|
|
9
|
+
'content-type': 'application/json',
|
|
10
|
+
},
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('BrninpayClient', () => {
|
|
15
|
+
it('signs requests with HMAC headers when apiSecret is configured', async () => {
|
|
16
|
+
const fetchMock = vi.fn(async () => createJsonResponse({ ok: true }))
|
|
17
|
+
const client = new BrninpayClient({
|
|
18
|
+
apiKey: 'key_123',
|
|
19
|
+
apiSecret: 'secret_123',
|
|
20
|
+
baseUrl: 'http://localhost:3000',
|
|
21
|
+
fetchImplementation: fetchMock as typeof fetch,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
await client.request({
|
|
25
|
+
path: '/v1/health',
|
|
26
|
+
method: 'POST',
|
|
27
|
+
body: { hello: 'world' },
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
31
|
+
const [, requestInit] = fetchMock.mock.calls[0] as [string, RequestInit]
|
|
32
|
+
const headers = new Headers(requestInit.headers)
|
|
33
|
+
|
|
34
|
+
expect(headers.get('X-API-Key')).toBe('key_123')
|
|
35
|
+
expect(headers.get('X-Signature')).toMatch(/^[0-9a-f]+$/)
|
|
36
|
+
expect(headers.get('X-Timestamp')).toContain('T')
|
|
37
|
+
expect(headers.get('X-Nonce')).toHaveLength(32)
|
|
38
|
+
expect(headers.get('Authorization')).toBeNull()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('retries transient failures with exponential backoff behavior', async () => {
|
|
42
|
+
const fetchMock = vi
|
|
43
|
+
.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>()
|
|
44
|
+
.mockResolvedValueOnce(createJsonResponse({ error: 'temporary' }, 500))
|
|
45
|
+
.mockResolvedValueOnce(createJsonResponse({ ok: true }, 200))
|
|
46
|
+
|
|
47
|
+
const client = new BrninpayClient({
|
|
48
|
+
apiKey: 'key_123',
|
|
49
|
+
baseUrl: 'http://localhost:3000',
|
|
50
|
+
fetchImplementation: fetchMock as typeof fetch,
|
|
51
|
+
retryPolicy: {
|
|
52
|
+
retries: 1,
|
|
53
|
+
baseDelayMs: 1,
|
|
54
|
+
maxDelayMs: 1,
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const response = await client.request<{ ok: boolean }>({
|
|
59
|
+
path: '/v1/health',
|
|
60
|
+
method: 'GET',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
expect(response.ok).toBe(true)
|
|
64
|
+
expect(fetchMock).toHaveBeenCalledTimes(2)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('maps API validation errors into typed SDK errors', async () => {
|
|
68
|
+
const fetchMock = vi.fn(async () =>
|
|
69
|
+
createJsonResponse(
|
|
70
|
+
{
|
|
71
|
+
code: 'validation_error',
|
|
72
|
+
message: 'invalid payload',
|
|
73
|
+
details: { field: 'amount' },
|
|
74
|
+
},
|
|
75
|
+
400
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const client = new BrninpayClient({
|
|
80
|
+
apiKey: 'key_123',
|
|
81
|
+
baseUrl: 'http://localhost:3000',
|
|
82
|
+
fetchImplementation: fetchMock as typeof fetch,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
await expect(
|
|
86
|
+
client.request({
|
|
87
|
+
path: '/v1/payment-intents',
|
|
88
|
+
method: 'POST',
|
|
89
|
+
body: { invalid: true },
|
|
90
|
+
})
|
|
91
|
+
).rejects.toBeInstanceOf(ValidationError)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('creates and parses payment intents with bigint amounts', async () => {
|
|
95
|
+
const fetchMock = vi.fn(async () =>
|
|
96
|
+
createJsonResponse({
|
|
97
|
+
id: 'pi_123',
|
|
98
|
+
workspaceId: 'ws_123',
|
|
99
|
+
actorId: 'actor_123',
|
|
100
|
+
status: 'approved',
|
|
101
|
+
amount: {
|
|
102
|
+
currency: 'USDC',
|
|
103
|
+
value: '1500000',
|
|
104
|
+
decimals: 6,
|
|
105
|
+
},
|
|
106
|
+
recipient: {
|
|
107
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
108
|
+
chainId: 8453,
|
|
109
|
+
},
|
|
110
|
+
createdAt: '2026-07-06T10:00:00.000Z',
|
|
111
|
+
updatedAt: '2026-07-06T10:00:00.000Z',
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const client = new BrninpayClient({
|
|
116
|
+
apiKey: 'key_123',
|
|
117
|
+
baseUrl: 'http://localhost:3000',
|
|
118
|
+
fetchImplementation: fetchMock as typeof fetch,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const paymentIntent = await client.paymentIntents.createPaymentIntent({
|
|
122
|
+
workspaceId: 'ws_123',
|
|
123
|
+
actorId: 'actor_123',
|
|
124
|
+
idempotencyKey: 'idem_123',
|
|
125
|
+
amount: {
|
|
126
|
+
currency: 'USDC',
|
|
127
|
+
value: 1500000n,
|
|
128
|
+
},
|
|
129
|
+
recipient: {
|
|
130
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
131
|
+
chainId: 8453,
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
expect(paymentIntent.amount.value).toBe(1500000n)
|
|
136
|
+
|
|
137
|
+
const [, requestInit] = fetchMock.mock.calls[0] as [string, RequestInit]
|
|
138
|
+
const headers = new Headers(requestInit.headers)
|
|
139
|
+
expect(headers.get('Idempotency-Key')).toBe('idem_123')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('lists payment intents and preserves pagination cursors', async () => {
|
|
143
|
+
const fetchMock = vi.fn(async () =>
|
|
144
|
+
createJsonResponse({
|
|
145
|
+
items: [
|
|
146
|
+
{
|
|
147
|
+
id: 'pi_123',
|
|
148
|
+
workspaceId: 'ws_123',
|
|
149
|
+
actorId: 'actor_123',
|
|
150
|
+
status: 'submitted',
|
|
151
|
+
amount: {
|
|
152
|
+
currency: 'USDC',
|
|
153
|
+
value: '1500000',
|
|
154
|
+
decimals: 6,
|
|
155
|
+
},
|
|
156
|
+
recipient: {
|
|
157
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
158
|
+
chainId: 8453,
|
|
159
|
+
},
|
|
160
|
+
createdAt: '2026-07-06T10:00:00.000Z',
|
|
161
|
+
updatedAt: '2026-07-06T10:00:00.000Z',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
nextCursor: 'cursor_2',
|
|
165
|
+
})
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const client = new BrninpayClient({
|
|
169
|
+
apiKey: 'key_123',
|
|
170
|
+
baseUrl: 'http://localhost:3000',
|
|
171
|
+
fetchImplementation: fetchMock as typeof fetch,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const result = await client.paymentIntents.listPaymentIntents({ limit: 10 })
|
|
175
|
+
|
|
176
|
+
expect(result.items).toHaveLength(1)
|
|
177
|
+
expect(result.items[0]?.status).toBe('submitted')
|
|
178
|
+
expect(result.nextCursor).toBe('cursor_2')
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { parseUsdc, formatUsdc } from '../src/conversion.js'
|
|
3
|
+
|
|
4
|
+
describe('parseUsdc', () => {
|
|
5
|
+
it('converts whole USDC to smallest unit', () => {
|
|
6
|
+
expect(parseUsdc('1')).toBe(1_000_000n)
|
|
7
|
+
expect(parseUsdc('100')).toBe(100_000_000n)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('converts decimal USDC to smallest unit', () => {
|
|
11
|
+
expect(parseUsdc('0.05')).toBe(50_000n)
|
|
12
|
+
expect(parseUsdc('1.5')).toBe(1_500_000n)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('handles very small amounts', () => {
|
|
16
|
+
expect(parseUsdc('0.000001')).toBe(1n)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('throws on invalid input', () => {
|
|
20
|
+
expect(() => parseUsdc('not-a-number')).toThrow()
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('formatUsdc', () => {
|
|
25
|
+
it('converts smallest unit to whole USDC string', () => {
|
|
26
|
+
expect(formatUsdc(1_000_000n)).toBe('1')
|
|
27
|
+
expect(formatUsdc(100_000_000n)).toBe('100')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('converts smallest unit to decimal USDC string', () => {
|
|
31
|
+
expect(formatUsdc(50_000n)).toBe('0.05')
|
|
32
|
+
expect(formatUsdc(1_500_000n)).toBe('1.5')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('handles zero', () => {
|
|
36
|
+
expect(formatUsdc(0n)).toBe('0')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('handles 1 micro USDC', () => {
|
|
40
|
+
expect(formatUsdc(1n)).toBe('0.000001')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { Runtimee } from '../src/runtimee.js'
|
|
3
|
+
import type { RuntimeeConfig } from '../src/types.js'
|
|
4
|
+
|
|
5
|
+
describe('Runtimee', () => {
|
|
6
|
+
const config: RuntimeeConfig = {
|
|
7
|
+
apiKey: 're_test_key',
|
|
8
|
+
baseUrl: 'http://localhost:3000',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
it('creates instance with config', () => {
|
|
12
|
+
const rt = new Runtimee(config)
|
|
13
|
+
expect(rt).toBeDefined()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('throws without apiKey', () => {
|
|
17
|
+
expect(() => new Runtimee({} as RuntimeeConfig)).toThrow('apiKey')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('exposes actors namespace', () => {
|
|
21
|
+
const rt = new Runtimee(config)
|
|
22
|
+
expect(rt.actors).toBeDefined()
|
|
23
|
+
expect(rt.paymentIntents).toBeDefined()
|
|
24
|
+
expect(rt.policies).toBeDefined()
|
|
25
|
+
expect(typeof rt.actors.create).toBe('function')
|
|
26
|
+
expect(typeof rt.actors.pay).toBe('function')
|
|
27
|
+
expect(typeof rt.actors.previewPay).toBe('function')
|
|
28
|
+
expect(typeof rt.actors.status).toBe('function')
|
|
29
|
+
})
|
|
30
|
+
})
|
package/tsconfig.json
ADDED