test(builder): offline FixtureProvider with captured testnet protocol parameters

This commit is contained in:
FilipBl4gojevic
2026-07-29 10:40:35 +02:00
parent 8cc8558fdd
commit 2d961c13fa
4 changed files with 827 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
// packages/builder/test/fixtures/capture-protocol-params.mjs
//
// One-time capture: writes Lucid-format protocol parameters from the live
// Vector testnet Ogmios endpoint into protocol-params.fixture.json, using
// OgmiosProvider's own mapping so the fixture can never drift from the
// conversion logic. Run manually after `npm run build`:
//
// node packages/builder/test/fixtures/capture-protocol-params.mjs
//
// Never runs in CI — unit tests read the checked-in JSON.
import { writeFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { OgmiosProvider } from '@apexfusion/vector-mcp-shared/provider';
const ogmiosUrl = process.env.VECTOR_OGMIOS_URL || 'https://ogmios.vector.testnet.apexfusion.org';
const provider = new OgmiosProvider({ ogmiosUrl, submitUrl: 'http://unused.invalid', koiosUrl: '' });
const params = await provider.getProtocolParameters();
const json = JSON.stringify(params, (_k, v) => (typeof v === 'bigint' ? { __bigint: v.toString() } : v), 2);
const out = resolve(dirname(fileURLToPath(import.meta.url)), 'protocol-params.fixture.json');
writeFileSync(out, json + '\n');
console.log(`wrote ${out}`);

View File

@@ -0,0 +1,40 @@
// packages/builder/test/fixtures/fixture-provider.test.ts
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { Lucid } from '@lucid-evolution/lucid';
import { FixtureProvider, fixtureProtocolParameters, FIXTURE_UTXOS, OWN_ADDRESS, FOREIGN_ADDRESS } from './fixture-provider.ts';
describe('FixtureProvider', () => {
test('serves the captured protocol parameters with bigints revived', () => {
const p = fixtureProtocolParameters();
assert.equal(p.minFeeA, 45);
assert.equal(p.minFeeB, 156253);
assert.equal(typeof p.coinsPerUtxoByte, 'bigint');
assert.ok(p.costModels.PlutusV2, 'PlutusV2 cost model missing from fixture');
});
test('getUtxos filters by address and returns nothing for a foreign address', async () => {
const provider = new FixtureProvider(FIXTURE_UTXOS);
assert.equal((await provider.getUtxos(OWN_ADDRESS)).length, 3);
assert.equal((await provider.getUtxos(FOREIGN_ADDRESS)).length, 0);
});
test('network-only methods throw loudly instead of hanging', async () => {
const provider = new FixtureProvider(FIXTURE_UTXOS);
await assert.rejects(() => provider.submitTx('84'), /offline/);
await assert.rejects(() => provider.getDelegation('stake1xyz'), /offline/);
});
// The offline replication of the PR-4 spike: a REAL unsigned transaction,
// built with no key and no network. If this test passes, every later build
// test has a working foundation.
test('Lucid + FixtureProvider builds a real unsigned tx via fromAddress', async () => {
const lucid = await Lucid(new FixtureProvider(FIXTURE_UTXOS), 'Mainnet');
const utxos = await lucid.utxosAt(OWN_ADDRESS);
lucid.selectWallet.fromAddress(OWN_ADDRESS, utxos);
const tx = await lucid.newTx().pay.ToAddress(FOREIGN_ADDRESS, { lovelace: 3_000_000n }).complete();
const cbor = tx.toCBOR();
assert.ok(cbor.length > 200, `unsigned tx CBOR suspiciously short: ${cbor.length} hex chars`);
assert.match(cbor, /^84/, 'Conway-era tx CBOR must start with array header 0x84');
});
});

View File

@@ -0,0 +1,87 @@
// packages/builder/test/fixtures/fixture-provider.ts
//
// Offline stand-in for OgmiosProvider in unit tests. Serves the checked-in
// protocol-params fixture (captured from the live testnet by
// capture-protocol-params.mjs) and a caller-supplied UTxO set. Every method
// that would need the chain throws loudly so a test that would touch the
// network fails fast instead of hanging or silently passing.
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import type {
Provider, ProtocolParameters, UTxO, OutRef, Address, Credential,
Delegation, DatumHash, Datum, RewardAddress, Transaction, TxHash, Unit,
} from '@lucid-evolution/lucid';
export const OWN_ADDRESS = 'addr1qylasu4y34ccwt8hv55tkswa9fthtck9xtppvc4y03kwhaztd0jlxdzk72zj8mgy6x4269v9gytkgffh3k8d3l8987yqu5c35z';
export const FOREIGN_ADDRESS = 'addr1wx434t2jc3m5uhdf7tq05xjdqu3q5z7a2lhrmn5mapsd43srh7ll8';
export const FIXTURE_TXHASH = 'e17a2ebab8eae3850f959290041b3bef3f3597584f8cf4a728e14777dddb8c32';
export const FIXTURE_TOKEN_UNIT = '1111111111111111111111111111111111111111111111111111111154657374546f6b656e';
export const FIXTURE_UTXOS: UTxO[] = [
{ txHash: FIXTURE_TXHASH, outputIndex: 0, address: OWN_ADDRESS, assets: { lovelace: 1_000_000_000n } },
{ txHash: FIXTURE_TXHASH, outputIndex: 1, address: OWN_ADDRESS, assets: { lovelace: 5_000_000n } },
{ txHash: FIXTURE_TXHASH, outputIndex: 2, address: OWN_ADDRESS, assets: { lovelace: 10_000_000n, [FIXTURE_TOKEN_UNIT]: 500n } },
];
const PARAMS_PATH = resolve(import.meta.dirname!, 'protocol-params.fixture.json');
export function fixtureProtocolParameters(): ProtocolParameters {
return JSON.parse(readFileSync(PARAMS_PATH, 'utf-8'), (_k, v) =>
v !== null && typeof v === 'object' && '__bigint' in v ? BigInt(v.__bigint) : v,
) as ProtocolParameters;
}
export class FixtureProvider implements Provider {
private utxos: UTxO[];
// Not a parameter-property constructor (`constructor(private utxos...)`):
// Node's native type-stripping (no ts-node/tsx loader in the plain `node
// --test` invocation this file is designed to run under) only erases
// types, it does not transform TS-only constructs like parameter
// properties, and rejects them with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.
// Same behavior as the parameter-property form, just spelled so `node`
// can strip-and-run it directly. Matches OgmiosProvider's own style in
// provider.ts, which never uses parameter properties either.
constructor(utxos: UTxO[]) {
this.utxos = utxos;
}
async getProtocolParameters(): Promise<ProtocolParameters> {
return fixtureProtocolParameters();
}
async getUtxos(addressOrCredential: Address | Credential): Promise<UTxO[]> {
if (typeof addressOrCredential !== 'string') {
throw new Error('FixtureProvider: credential queries not supported offline');
}
return this.utxos.filter((u) => u.address === addressOrCredential);
}
async getUtxosByOutRef(outRefs: Array<OutRef>): Promise<UTxO[]> {
return this.utxos.filter((u) =>
outRefs.some((r) => r.txHash === u.txHash && Number(r.outputIndex) === u.outputIndex),
);
}
async getUtxosWithUnit(_a: Address | Credential, _u: Unit): Promise<UTxO[]> {
throw new Error('FixtureProvider: offline — getUtxosWithUnit unavailable');
}
async getUtxoByUnit(_u: Unit): Promise<UTxO> {
throw new Error('FixtureProvider: offline — getUtxoByUnit unavailable');
}
async getDelegation(_r: RewardAddress): Promise<Delegation> {
throw new Error('FixtureProvider: offline — getDelegation unavailable');
}
async getDatum(_d: DatumHash): Promise<Datum> {
throw new Error('FixtureProvider: offline — getDatum unavailable');
}
awaitTx(_tx: TxHash, _interval?: number): Promise<boolean> {
throw new Error('FixtureProvider: offline — awaitTx unavailable');
}
async submitTx(_tx: Transaction): Promise<TxHash> {
throw new Error('FixtureProvider: offline — submitTx unavailable');
}
async evaluateTx(_tx: Transaction, _additional?: UTxO[]): Promise<any> {
throw new Error('FixtureProvider: offline — evaluateTx unavailable');
}
}

View File

@@ -0,0 +1,678 @@
{
"minFeeA": 45,
"minFeeB": 156253,
"maxTxSize": 16384,
"maxValSize": 5000,
"keyDeposit": {
"__bigint": "500000000"
},
"poolDeposit": {
"__bigint": "5000000000000"
},
"priceMem": 0.0577,
"priceStep": 0.0000721,
"maxTxExMem": {
"__bigint": "16000000"
},
"maxTxExSteps": {
"__bigint": "10000000000"
},
"coinsPerUtxoByte": {
"__bigint": "4310"
},
"collateralPercentage": 150,
"maxCollateralInputs": 3,
"costModels": {
"PlutusV1": [
197209,
0,
1,
1,
396231,
621,
0,
1,
150000,
1000,
0,
1,
150000,
32,
2477736,
29175,
4,
29773,
100,
29773,
100,
29773,
100,
29773,
100,
29773,
100,
29773,
100,
100,
100,
29773,
100,
150000,
32,
150000,
32,
150000,
32,
150000,
1000,
0,
1,
150000,
32,
150000,
1000,
0,
8,
148000,
425507,
118,
0,
1,
1,
150000,
1000,
0,
8,
150000,
112536,
247,
1,
150000,
10000,
1,
136542,
1326,
1,
1000,
150000,
1000,
1,
150000,
32,
150000,
32,
150000,
32,
1,
1,
150000,
1,
150000,
4,
103599,
248,
1,
103599,
248,
1,
145276,
1366,
1,
179690,
497,
1,
150000,
32,
150000,
32,
150000,
32,
150000,
32,
150000,
32,
150000,
32,
148000,
425507,
118,
0,
1,
1,
61516,
11218,
0,
1,
150000,
32,
148000,
425507,
118,
0,
1,
1,
148000,
425507,
118,
0,
1,
1,
2477736,
29175,
4,
0,
82363,
4,
150000,
5000,
0,
1,
150000,
32,
197209,
0,
1,
1,
150000,
32,
150000,
32,
150000,
32,
150000,
32,
150000,
32,
150000,
32,
150000,
32,
3345831,
1,
1
],
"PlutusV2": [
205665,
812,
1,
1,
1000,
571,
0,
1,
1000,
24177,
4,
1,
1000,
32,
117366,
10475,
4,
23000,
100,
23000,
100,
23000,
100,
23000,
100,
23000,
100,
23000,
100,
100,
100,
23000,
100,
19537,
32,
175354,
32,
46417,
4,
221973,
511,
0,
1,
89141,
32,
497525,
14068,
4,
2,
196500,
453240,
220,
0,
1,
1,
1000,
28662,
4,
2,
245000,
216773,
62,
1,
1060367,
12586,
1,
208512,
421,
1,
187000,
1000,
52998,
1,
80436,
32,
43249,
32,
1000,
32,
80556,
1,
57667,
4,
1000,
10,
197145,
156,
1,
197145,
156,
1,
204924,
473,
1,
208896,
511,
1,
52467,
32,
64832,
32,
65493,
32,
22558,
32,
16563,
32,
76511,
32,
196500,
453240,
220,
0,
1,
1,
69522,
11687,
0,
1,
60091,
32,
196500,
453240,
220,
0,
1,
1,
196500,
453240,
220,
0,
1,
1,
1159724,
392670,
0,
2,
806990,
30482,
4,
1927926,
82523,
4,
265318,
0,
4,
0,
85931,
32,
205665,
812,
1,
1,
41182,
32,
212342,
32,
31220,
32,
32696,
32,
43357,
32,
32247,
32,
38314,
32,
35892428,
10,
9462713,
1021,
10,
38887044,
32947,
10
],
"PlutusV3": [
100788,
420,
1,
1,
1000,
173,
0,
1,
1000,
59957,
4,
1,
11183,
32,
201305,
8356,
4,
16000,
100,
16000,
100,
16000,
100,
16000,
100,
16000,
100,
16000,
100,
100,
100,
16000,
100,
94375,
32,
132994,
32,
61462,
4,
72010,
178,
0,
1,
22151,
32,
91189,
769,
4,
2,
85848,
123203,
7305,
-900,
1716,
549,
57,
85848,
0,
1,
1,
1000,
42921,
4,
2,
24548,
29498,
38,
1,
898148,
27279,
1,
51775,
558,
1,
39184,
1000,
60594,
1,
141895,
32,
83150,
32,
15299,
32,
76049,
1,
13169,
4,
22100,
10,
28999,
74,
1,
28999,
74,
1,
43285,
552,
1,
44749,
541,
1,
33852,
32,
68246,
32,
72362,
32,
7243,
32,
7391,
32,
11546,
32,
85848,
123203,
7305,
-900,
1716,
549,
57,
85848,
0,
1,
90434,
519,
0,
1,
74433,
32,
85848,
123203,
7305,
-900,
1716,
549,
57,
85848,
0,
1,
1,
85848,
123203,
7305,
-900,
1716,
549,
57,
85848,
0,
1,
955506,
213312,
0,
2,
270652,
22588,
4,
1457325,
64566,
4,
20467,
1,
4,
0,
141992,
32,
100788,
420,
1,
1,
81663,
32,
59498,
32,
20142,
32,
24588,
32,
20744,
32,
25933,
32,
24623,
32,
43053543,
10,
53384111,
14333,
10,
43574283,
26308,
10,
16000,
100,
16000,
100,
962335,
18,
2780678,
6,
442008,
1,
52538055,
3756,
18,
267929,
18,
76433006,
8868,
18,
52948122,
18,
1995836,
36,
3227919,
12,
901022,
1,
166917843,
4307,
36,
284546,
36,
158221314,
26549,
36,
74698472,
36,
333849714,
1,
254006273,
72,
2174038,
72,
2261318,
64571,
4,
207616,
8310,
4,
1293828,
28716,
63,
0,
1,
1006041,
43623,
251,
0,
1,
100181,
726,
719,
0,
1,
100181,
726,
719,
0,
1,
100181,
726,
719,
0,
1,
107878,
680,
0,
1,
95336,
1,
281145,
18848,
0,
1,
180194,
159,
1,
1,
158519,
8942,
0,
1,
159378,
8813,
0,
1,
107490,
3298,
1,
106057,
655,
1,
1964219,
24520,
3
]
},
"minFeeRefScriptCostPerByte": 15,
"drepDeposit": {
"__bigint": "1000000000000"
},
"govActionDeposit": {
"__bigint": "1000000000"
}
}