mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
Replaces the runUtila STUB with a real evm_personal_sign attestation
flow against @luxfi/utila Connect-RPC. After this commit the layered
cosigner feature is feature-complete for BOTH providers — no stubs
remain in app/server/src/domain/cosigners.ts.
Flow:
1. Build per-tenant gRPC client via createGrpcClient + serviceAccount-
AuthStrategy with { email: intent.org_id, privateKey: () => secretPem }
— mirrors the env-singleton client in domain/utila.ts but isolates
each tenant's auth context so multiple tenants cosign in parallel
without singleton contention.
2. initiateTransaction({
parent: vaults/<intent.vault_id>,
details: { details: { case: 'evmPersonalSign', value: {
fromAddress: intent.client_id, messageHex: opts.txHash
} } },
priority: NORMAL, note: 'lux-bridge cosign swap=<id>'
})
vault_id is REQUIRED — utila scopes initiateTransaction by parent
vault. Missing vault_id returns 'failed' before any RPC.
3. Poll getTransaction(name) every 1.5s, partition Utila's
TransactionState_Enum (proto path:
pkg/utila/src/lib/gen/utila/api/v1/transactions_pb.ts) into:
approved SIGNED=4, AWAITING_PUBLISH=5, PUBLISHED=6,
MINED=7, CONFIRMED=13
rejected DECLINED=9, CANCELED=11, DROPPED=12, EXPIRED=15
failed FAILED=8, REPLACED=10
If state is in 'approved' but evmMessage.signature isn't populated
yet (Utila publishes the sig in a later state transition), keep
polling — don't bail. Intermediates (AWAITING_APPROVAL=2,
AWAITING_POLICY_CHECK=14, AWAITING_SIGNATURE=3) also keep polling.
4. 60s timeout ceiling (UTILA_COSIGNER_TIMEOUT_MS env override).
Dynamic read on each call so config picks up without a restart.
5. Return CosignResult with signature converted from Uint8Array proto
bytes to 0x-prefixed hex; externalId = Utila transaction.name.
Edge cases covered by 13 tests (mocked @luxfi/utila):
- happy: SIGNED + signature → approved
- happy: CONFIRMED (later state, sig already attested) → approved
- happy: SIGNED-without-sig keeps polling, picks up next tick
- rejected: DECLINED, CANCELED, EXPIRED, DROPPED — all map to rejected
- failed: FAILED proto state
- failed: initiateTransaction throws (auth: bad jwt)
- failed: initiateTransaction returns no transaction.name
- failed: timeout (AWAITING_* indefinitely)
- failed: missing vault_id → fail BEFORE any RPC call
- failed: env-var fallback unset → clear KMS-TODO error
- wire-shape assertion: initiateTransaction is called with the right
parent + evmPersonalSign + fromAddress + messageHex + note
Test infra:
- vitest.config.ts gains a resolve.alias mapping @luxfi/utila to its
workspace src/grpc-client.ts. The published 3.0.0 rollup-built
dist/ is what npm consumers see, but the workspace package's
local rollup config has a long-standing outDir-vs-file-path issue
that prevents in-place builds. Vitest resolves the TS source
directly and runtime production is unaffected.
Tests: 91 → 104 total (58 SDK + 46 server).
Server breakdown: 16 validator + 10 Fireblocks + 7 persistence + 13 Utila.
After this commit there are no STUBs in the cosigner module. The
remaining work on #386 is the KMS round-trip in fetchCosignerSecret
(env-var fallback works for dev; production needs KMS path read).
User correction: 'utila was already 100% complete... please make sure
it's done' — addressed by replacing the runUtila STUB with the real
implementation that reuses the @luxfi/utila Connect-RPC machinery
already in the workspace.
28 lines
1.0 KiB
TypeScript
28 lines
1.0 KiB
TypeScript
import { defineConfig } from "vitest/config"
|
|
import { fileURLToPath } from "node:url"
|
|
import path from "node:path"
|
|
|
|
// Server-side unit tests. Mirrors the `@/*` path alias used by the
|
|
// runtime (see tsconfig.json) so tests import the same way as src code.
|
|
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
export default defineConfig({
|
|
test: {
|
|
environment: "node",
|
|
include: ["src/**/__tests__/*.test.ts"],
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
"@": path.resolve(here, "src"),
|
|
// @luxfi/utila ships from a published rollup-built dist/ to npm,
|
|
// but the workspace dist/ is not built in-place (the rollup
|
|
// config has a long-standing outDir-vs-file-path issue). For
|
|
// tests, point at the TypeScript source directly — vitest can
|
|
// handle TS imports natively. Production builds resolve through
|
|
// the package.json main field once the published artifact is
|
|
// installed from npm.
|
|
"@luxfi/utila": path.resolve(here, "../../pkg/utila/src/grpc-client.ts"),
|
|
},
|
|
},
|
|
})
|