@crediolabs/policy-synth 0.1.3 → 0.1.5
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/LICENSE +21 -0
- package/README.md +193 -5
- package/dist/adapters/interpreter/adapter.d.ts +38 -0
- package/dist/adapters/interpreter/adapter.js +522 -0
- package/dist/adapters/interpreter/index.d.ts +1 -0
- package/dist/adapters/interpreter/index.js +2 -0
- package/dist/adapters/oz/adapter.js +2 -0
- package/dist/codegen/compile-gate.d.ts +33 -0
- package/dist/codegen/compile-gate.js +119 -0
- package/dist/codegen/index.d.ts +2 -0
- package/dist/codegen/index.js +8 -0
- package/dist/codegen/template.d.ts +18 -0
- package/dist/codegen/template.js +131 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/ir/types.d.ts +13 -2
- package/dist/predicate/encode.d.ts +10 -0
- package/dist/predicate/encode.js +249 -0
- package/dist/predicate/index.d.ts +1 -0
- package/dist/predicate/index.js +2 -0
- package/dist/record/decode.d.ts +6 -3
- package/dist/record/decode.js +13 -3
- package/dist/review-card/builder.d.ts +14 -0
- package/dist/review-card/builder.js +261 -0
- package/dist/review-card/conflict.d.ts +40 -0
- package/dist/review-card/conflict.js +111 -0
- package/dist/review-card/cross-check.d.ts +11 -0
- package/dist/review-card/cross-check.js +148 -0
- package/dist/review-card/index.d.ts +3 -0
- package/dist/review-card/index.js +4 -0
- package/dist/run/index.d.ts +31 -0
- package/dist/run/index.js +164 -0
- package/dist/run/schemas.d.ts +2003 -0
- package/dist/run/schemas.js +215 -0
- package/dist/synth/compose-from-recording.d.ts +35 -7
- package/dist/synth/compose-from-recording.js +225 -34
- package/dist/synth/deny-cases.d.ts +12 -0
- package/dist/synth/deny-cases.js +377 -0
- package/dist/synth/evaluate.d.ts +39 -0
- package/dist/synth/evaluate.js +425 -0
- package/dist/synth/harness.d.ts +16 -0
- package/dist/synth/harness.js +26 -0
- package/dist/synth/index.d.ts +4 -0
- package/dist/synth/index.js +4 -0
- package/dist/synth/minimize.d.ts +4 -0
- package/dist/synth/minimize.js +38 -0
- package/dist/synth/predicate-literals.d.ts +5 -0
- package/dist/synth/predicate-literals.js +25 -0
- package/dist/synth/synthesize-from-recording.d.ts +37 -5
- package/dist/synth/synthesize-from-recording.js +550 -18
- package/dist/types.d.ts +40 -1
- package/dist/types.js +17 -0
- package/dist/verify/envelope.d.ts +15 -0
- package/dist/verify/envelope.js +22 -0
- package/dist/verify/index.d.ts +3 -0
- package/dist/verify/index.js +3 -0
- package/dist/verify/simulate.d.ts +31 -0
- package/dist/verify/simulate.js +258 -0
- package/dist/verify/verify.d.ts +21 -0
- package/dist/verify/verify.js +189 -0
- package/dist-cjs/adapters/interpreter/adapter.d.ts +38 -0
- package/dist-cjs/adapters/interpreter/adapter.js +527 -0
- package/dist-cjs/adapters/interpreter/index.d.ts +1 -0
- package/dist-cjs/adapters/interpreter/index.js +8 -0
- package/dist-cjs/adapters/oz/adapter.d.ts +20 -0
- package/dist-cjs/adapters/oz/adapter.js +289 -0
- package/dist-cjs/adapters/oz/index.d.ts +1 -0
- package/dist-cjs/adapters/oz/index.js +8 -0
- package/dist-cjs/codegen/compile-gate.d.ts +33 -0
- package/dist-cjs/codegen/compile-gate.js +123 -0
- package/dist-cjs/codegen/index.d.ts +2 -0
- package/dist-cjs/codegen/index.js +14 -0
- package/dist-cjs/codegen/template.d.ts +18 -0
- package/dist-cjs/codegen/template.js +134 -0
- package/dist-cjs/errors.d.ts +37 -0
- package/dist-cjs/errors.js +3 -0
- package/dist-cjs/index.d.ts +11 -0
- package/dist-cjs/index.js +27 -0
- package/dist-cjs/ir/index.d.ts +1 -0
- package/dist-cjs/ir/index.js +3 -0
- package/dist-cjs/ir/types.d.ts +108 -0
- package/dist-cjs/ir/types.js +12 -0
- package/dist-cjs/mandate/index.d.ts +2 -0
- package/dist-cjs/mandate/index.js +6 -0
- package/dist-cjs/mandate/to-ir.d.ts +3 -0
- package/dist-cjs/mandate/to-ir.js +63 -0
- package/dist-cjs/mandate/types.d.ts +20 -0
- package/dist-cjs/mandate/types.js +9 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/predicate/encode.d.ts +10 -0
- package/dist-cjs/predicate/encode.js +252 -0
- package/dist-cjs/predicate/index.d.ts +1 -0
- package/dist-cjs/predicate/index.js +6 -0
- package/dist-cjs/record/decode.d.ts +79 -0
- package/dist-cjs/record/decode.js +398 -0
- package/dist-cjs/record/freshness.d.ts +17 -0
- package/dist-cjs/record/freshness.js +55 -0
- package/dist-cjs/record/index.d.ts +21 -0
- package/dist-cjs/record/index.js +166 -0
- package/dist-cjs/record/movements.d.ts +20 -0
- package/dist-cjs/record/movements.js +192 -0
- package/dist-cjs/record/rpc.d.ts +22 -0
- package/dist-cjs/record/rpc.js +74 -0
- package/dist-cjs/record/validate.d.ts +22 -0
- package/dist-cjs/record/validate.js +63 -0
- package/dist-cjs/registry/identify.d.ts +11 -0
- package/dist-cjs/registry/identify.js +87 -0
- package/dist-cjs/registry/index.d.ts +3 -0
- package/dist-cjs/registry/index.js +15 -0
- package/dist-cjs/registry/known-addresses.d.ts +16 -0
- package/dist-cjs/registry/known-addresses.js +53 -0
- package/dist-cjs/registry/protocols.d.ts +38 -0
- package/dist-cjs/registry/protocols.js +153 -0
- package/dist-cjs/review-card/builder.d.ts +14 -0
- package/dist-cjs/review-card/builder.js +264 -0
- package/dist-cjs/review-card/conflict.d.ts +40 -0
- package/dist-cjs/review-card/conflict.js +114 -0
- package/dist-cjs/review-card/cross-check.d.ts +11 -0
- package/dist-cjs/review-card/cross-check.js +151 -0
- package/dist-cjs/review-card/index.d.ts +3 -0
- package/dist-cjs/review-card/index.js +10 -0
- package/dist-cjs/run/index.d.ts +31 -0
- package/dist-cjs/run/index.js +178 -0
- package/dist-cjs/run/schemas.d.ts +2003 -0
- package/dist-cjs/run/schemas.js +218 -0
- package/dist-cjs/seams/index.d.ts +1 -0
- package/dist-cjs/seams/index.js +3 -0
- package/dist-cjs/seams/types.d.ts +66 -0
- package/dist-cjs/seams/types.js +12 -0
- package/dist-cjs/synth/compose-from-recording.d.ts +64 -0
- package/dist-cjs/synth/compose-from-recording.js +356 -0
- package/dist-cjs/synth/deny-cases.d.ts +12 -0
- package/dist-cjs/synth/deny-cases.js +380 -0
- package/dist-cjs/synth/evaluate.d.ts +39 -0
- package/dist-cjs/synth/evaluate.js +428 -0
- package/dist-cjs/synth/harness.d.ts +16 -0
- package/dist-cjs/synth/harness.js +29 -0
- package/dist-cjs/synth/index.d.ts +9 -0
- package/dist-cjs/synth/index.js +23 -0
- package/dist-cjs/synth/lower.d.ts +23 -0
- package/dist-cjs/synth/lower.js +119 -0
- package/dist-cjs/synth/minimize.d.ts +4 -0
- package/dist-cjs/synth/minimize.js +41 -0
- package/dist-cjs/synth/predicate-literals.d.ts +5 -0
- package/dist-cjs/synth/predicate-literals.js +28 -0
- package/dist-cjs/synth/scope.d.ts +26 -0
- package/dist-cjs/synth/scope.js +81 -0
- package/dist-cjs/synth/synthesize-from-mandate.d.ts +5 -0
- package/dist-cjs/synth/synthesize-from-mandate.js +37 -0
- package/dist-cjs/synth/synthesize-from-recording.d.ts +49 -0
- package/dist-cjs/synth/synthesize-from-recording.js +711 -0
- package/dist-cjs/types.d.ts +288 -0
- package/dist-cjs/types.js +55 -0
- package/dist-cjs/verify/envelope.d.ts +15 -0
- package/dist-cjs/verify/envelope.js +23 -0
- package/dist-cjs/verify/index.d.ts +3 -0
- package/dist-cjs/verify/index.js +8 -0
- package/dist-cjs/verify/simulate.d.ts +31 -0
- package/dist-cjs/verify/simulate.js +261 -0
- package/dist-cjs/verify/verify.d.ts +21 -0
- package/dist-cjs/verify/verify.js +192 -0
- package/package.json +43 -3
- package/src/adapters/interpreter/adapter.ts +642 -0
- package/src/adapters/interpreter/index.ts +8 -0
- package/src/adapters/oz/adapter.ts +2 -0
- package/src/codegen/compile-gate.ts +162 -0
- package/src/codegen/index.ts +17 -0
- package/src/codegen/template.ts +148 -0
- package/src/errors.ts +2 -0
- package/src/index.ts +2 -0
- package/src/ir/types.ts +9 -2
- package/src/predicate/encode.ts +307 -0
- package/src/predicate/index.ts +3 -0
- package/src/record/decode.ts +21 -10
- package/src/review-card/builder.ts +303 -0
- package/src/review-card/conflict.ts +143 -0
- package/src/review-card/cross-check.ts +158 -0
- package/src/review-card/index.ts +12 -0
- package/src/run/index.ts +213 -0
- package/src/run/schemas.ts +242 -0
- package/src/synth/compose-from-recording.ts +277 -43
- package/src/synth/deny-cases.ts +466 -0
- package/src/synth/evaluate.ts +493 -0
- package/src/synth/harness.ts +40 -0
- package/src/synth/index.ts +12 -0
- package/src/synth/minimize.ts +44 -0
- package/src/synth/predicate-literals.ts +27 -0
- package/src/synth/synthesize-from-recording.ts +655 -19
- package/src/types.ts +38 -2
- package/src/verify/envelope.ts +28 -0
- package/src/verify/index.ts +5 -0
- package/src/verify/simulate.ts +311 -0
- package/src/verify/verify.ts +243 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/record/movements.ts - raw on-chain contract events -> TokenMovement[].
|
|
3
|
+
//
|
|
4
|
+
// SEP-41 / SAC `transfer` / `mint` / `burn` events emit topics shaped as:
|
|
5
|
+
// topics[0] = Symbol("transfer") | "mint" | "burn"
|
|
6
|
+
// topics[1] = Address (from) (transfer only - omitted for mint; burn "from" is the caller)
|
|
7
|
+
// topics[2] = Address (to) (transfer + mint; burn uses a single from)
|
|
8
|
+
// data = Vec<ScVal> { Address to, I128 amount } (for transfer on classic SAC)
|
|
9
|
+
// OR I128 amount directly (for some token flavours)
|
|
10
|
+
//
|
|
11
|
+
// We accept both shapes; the validator reuses these as keys to cross-check
|
|
12
|
+
// against the parsed invocation args.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.extractTokenMovements = extractTokenMovements;
|
|
15
|
+
exports.readAmount = readAmount;
|
|
16
|
+
exports.parseContractEventToMovement = parseContractEventToMovement;
|
|
17
|
+
const decode_ts_1 = require("./decode.js");
|
|
18
|
+
const TRANSFER = 'transfer';
|
|
19
|
+
const MINT = 'mint';
|
|
20
|
+
const BURN = 'burn';
|
|
21
|
+
/** Extract TokenMovement[] from a list of raw on-chain events. Pure: no IO,
|
|
22
|
+
* no network. Non-movement events are ignored; malformed movement events are
|
|
23
|
+
* recorded as opaque so the recorder can fail closed. */
|
|
24
|
+
function extractTokenMovements(events, opaqueScVals = []) {
|
|
25
|
+
const out = [];
|
|
26
|
+
events.forEach((evt, index) => {
|
|
27
|
+
try {
|
|
28
|
+
const moved = parseSingleEvent(evt);
|
|
29
|
+
if (moved) {
|
|
30
|
+
out.push(moved);
|
|
31
|
+
}
|
|
32
|
+
else if (isMovementEvent(evt)) {
|
|
33
|
+
opaqueScVals.push({ path: `events[${index}]`, type: movementDecodeFailure(evt) });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
opaqueScVals.push({ path: `events[${index}]`, type: 'undecodable-token-movement' });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function isMovementEvent(evt) {
|
|
43
|
+
const op = evt.topics[0];
|
|
44
|
+
return op === TRANSFER || op === MINT || op === BURN;
|
|
45
|
+
}
|
|
46
|
+
function movementDecodeFailure(evt) {
|
|
47
|
+
const address = evt.topics.slice(1).find((topic) => topic.startsWith('M'));
|
|
48
|
+
return address ? 'unsupported-muxed-address-topic' : 'undecodable-token-movement';
|
|
49
|
+
}
|
|
50
|
+
function parseSingleEvent(evt) {
|
|
51
|
+
const [head, second, third] = evt.topics;
|
|
52
|
+
if (!head)
|
|
53
|
+
return null;
|
|
54
|
+
const op = head;
|
|
55
|
+
if (op !== TRANSFER && op !== MINT && op !== BURN)
|
|
56
|
+
return null;
|
|
57
|
+
// Read the `amount` from the event data; the contract address is the emitter.
|
|
58
|
+
const token = evt.contract;
|
|
59
|
+
if (!token)
|
|
60
|
+
return null;
|
|
61
|
+
const amount = readAmount(evt.data);
|
|
62
|
+
if (amount === null)
|
|
63
|
+
return null;
|
|
64
|
+
if (op === TRANSFER) {
|
|
65
|
+
// topics[1] = from, topics[2] = to
|
|
66
|
+
const from = second ? addressTopic(second) : null;
|
|
67
|
+
const to = third ? addressTopic(third) : null;
|
|
68
|
+
if (!from || !to)
|
|
69
|
+
return null;
|
|
70
|
+
return { token, from, to, amount };
|
|
71
|
+
}
|
|
72
|
+
if (op === MINT) {
|
|
73
|
+
// topics[1] = admin (optional), topics[2] = to (sometimes). Some SEP-41 mints
|
|
74
|
+
// only emit one address topic. Best-effort: prefer topics[2], then data.to.
|
|
75
|
+
const to = third ? addressTopic(third) : readDataAddress(evt.data, 0);
|
|
76
|
+
if (!to)
|
|
77
|
+
return null;
|
|
78
|
+
return { token, from: 'mint', to, amount };
|
|
79
|
+
}
|
|
80
|
+
// BURN
|
|
81
|
+
// topics[1] = from (the burned holder). Some burns also surface a "from" in data.
|
|
82
|
+
const from = second ? addressTopic(second) : readDataAddress(evt.data, 0);
|
|
83
|
+
if (!from)
|
|
84
|
+
return null;
|
|
85
|
+
return { token, from, to: 'burn', amount };
|
|
86
|
+
}
|
|
87
|
+
/** Read an I128 amount from an event data ScVal. Supports:
|
|
88
|
+
* - data is I128 directly
|
|
89
|
+
* - data is a Vec whose [0] (or [1] for SAC transfer) is I128
|
|
90
|
+
* - data is a Vec whose [0] is a Map { 'amount': I128 }
|
|
91
|
+
* - data is U64 (fallback for amount fields encoded as u64) */
|
|
92
|
+
function readAmount(data) {
|
|
93
|
+
if (data.type === 'i128')
|
|
94
|
+
return data.value;
|
|
95
|
+
if (data.type === 'u64')
|
|
96
|
+
return data.value;
|
|
97
|
+
if (data.type !== 'vec')
|
|
98
|
+
return null;
|
|
99
|
+
const v = data.value;
|
|
100
|
+
// SAC transfer event data shape (some clients): Vec<Address, I128>
|
|
101
|
+
for (const item of v) {
|
|
102
|
+
if (item.type === 'i128')
|
|
103
|
+
return item.value;
|
|
104
|
+
if (item.type === 'u64')
|
|
105
|
+
return item.value;
|
|
106
|
+
}
|
|
107
|
+
// Some flavours wrap in a Map. We expose Map entries as other; bail.
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/** Extract a single address from an event data ScVal at the given vec index. */
|
|
111
|
+
function readDataAddress(data, index) {
|
|
112
|
+
if (data.type !== 'vec')
|
|
113
|
+
return null;
|
|
114
|
+
const item = data.value[index];
|
|
115
|
+
if (item?.type !== 'address')
|
|
116
|
+
return null;
|
|
117
|
+
return item.value;
|
|
118
|
+
}
|
|
119
|
+
/** Convert a topic-string back into an address strkey if it looks like one.
|
|
120
|
+
* Topics we previously encoded as `<kind>` (non-address fallback) are left
|
|
121
|
+
* alone. */
|
|
122
|
+
function addressTopic(s) {
|
|
123
|
+
if (s.startsWith('G') || s.startsWith('C'))
|
|
124
|
+
return s;
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
/** Re-decode a single ContractEvent body into a TokenMovement-shaped tuple,
|
|
128
|
+
* given the raw xdr.ScVal form (NOT the OnChainEvent subset). Used by the
|
|
129
|
+
* integration test to compare against the parse path without depending on
|
|
130
|
+
* the OnChainEvent subset. */
|
|
131
|
+
function parseContractEventToMovement(emitterC, body) {
|
|
132
|
+
const topics = body.topics();
|
|
133
|
+
if (topics.length === 0)
|
|
134
|
+
return null;
|
|
135
|
+
const head = topics[0];
|
|
136
|
+
if (!head)
|
|
137
|
+
return null;
|
|
138
|
+
const op = head.switch().name === 'scvSymbol' ? head.sym().toString() : '';
|
|
139
|
+
if (op !== TRANSFER && op !== MINT && op !== BURN)
|
|
140
|
+
return null;
|
|
141
|
+
const data = body.data();
|
|
142
|
+
const amountScval = pickAmountScval(data);
|
|
143
|
+
const amount = amountScval
|
|
144
|
+
? amountScval.switch().name === 'scvI128'
|
|
145
|
+
? (0, decode_ts_1.i128PartsToBigInt)(amountScval.i128()).toString()
|
|
146
|
+
: amountScval.switch().name === 'scvU64'
|
|
147
|
+
? (0, decode_ts_1.u64PartsToBigInt)(amountScval.u64()).toString()
|
|
148
|
+
: null
|
|
149
|
+
: null;
|
|
150
|
+
if (amount === null)
|
|
151
|
+
return null;
|
|
152
|
+
if (op === TRANSFER) {
|
|
153
|
+
const from = topics[1] && topics[1].switch().name === 'scvAddress'
|
|
154
|
+
? (0, decode_ts_1.decodeScAddressToAnyStrkey)(topics[1].address())
|
|
155
|
+
: null;
|
|
156
|
+
const to = topics[2] && topics[2].switch().name === 'scvAddress'
|
|
157
|
+
? (0, decode_ts_1.decodeScAddressToAnyStrkey)(topics[2].address())
|
|
158
|
+
: null;
|
|
159
|
+
if (!from || !to)
|
|
160
|
+
return null;
|
|
161
|
+
return { token: emitterC, from, to, amount };
|
|
162
|
+
}
|
|
163
|
+
if (op === MINT) {
|
|
164
|
+
const to = topics[2] && topics[2].switch().name === 'scvAddress'
|
|
165
|
+
? (0, decode_ts_1.decodeScAddressToAnyStrkey)(topics[2].address())
|
|
166
|
+
: null;
|
|
167
|
+
if (!to)
|
|
168
|
+
return null;
|
|
169
|
+
return { token: emitterC, from: 'mint', to, amount };
|
|
170
|
+
}
|
|
171
|
+
// BURN
|
|
172
|
+
const from = topics[1] && topics[1].switch().name === 'scvAddress'
|
|
173
|
+
? (0, decode_ts_1.decodeScAddressToAnyStrkey)(topics[1].address())
|
|
174
|
+
: null;
|
|
175
|
+
if (!from)
|
|
176
|
+
return null;
|
|
177
|
+
return { token: emitterC, from, to: 'burn', amount };
|
|
178
|
+
}
|
|
179
|
+
function pickAmountScval(data) {
|
|
180
|
+
const kind = data.switch().name;
|
|
181
|
+
if (kind === 'scvI128' || kind === 'scvU64')
|
|
182
|
+
return data;
|
|
183
|
+
if (kind !== 'scvVec')
|
|
184
|
+
return null;
|
|
185
|
+
const arr = data.vec() ?? [];
|
|
186
|
+
for (const item of arr) {
|
|
187
|
+
const k = item.switch().name;
|
|
188
|
+
if (k === 'scvI128' || k === 'scvU64')
|
|
189
|
+
return item;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { rpc, type xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
import type { Network } from '../types.ts';
|
|
3
|
+
/** A trimmed-down view of `rpc.Api.GetSuccessfulTransactionResponse` plus the
|
|
4
|
+
* failed-shape variant. Both are produced by Soroban RPC `getTransaction` on
|
|
5
|
+
* a successful or failed tx; missing/pending returns `null`. */
|
|
6
|
+
export interface SorobanTxResponse {
|
|
7
|
+
status: 'SUCCESS' | 'FAILED';
|
|
8
|
+
ledger: number;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
txHash: string;
|
|
11
|
+
envelopeXdr: xdr.TransactionEnvelope;
|
|
12
|
+
resultXdr?: xdr.TransactionResult;
|
|
13
|
+
resultMetaXdr?: xdr.TransactionMeta;
|
|
14
|
+
events: rpc.Api.TransactionEvents;
|
|
15
|
+
}
|
|
16
|
+
export type RpcFetcher = (hash: string) => Promise<SorobanTxResponse | null>;
|
|
17
|
+
/** Build a fetcher backed by the public Soroban RPC for the given network.
|
|
18
|
+
* Injectable by tests via the `fetcher` parameter to `recordTransaction`. */
|
|
19
|
+
export declare function createRpcServer(network: Network): RpcFetcher;
|
|
20
|
+
/** Lightweight reachability probe used by the integration test to decide whether
|
|
21
|
+
* to run or self-skip. Sends a JSON-RPC `getHealth` to the public endpoint. */
|
|
22
|
+
export declare function probeNetwork(network: Network, timeoutMs?: number): Promise<boolean>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/record/rpc.ts - isolates the network side-effect from the rest of the recorder.
|
|
3
|
+
//
|
|
4
|
+
// Everything else in the recorder (decode, movements, freshness, validate) is pure.
|
|
5
|
+
// Unit tests inject a fetcher so they never hit the network; the integration test
|
|
6
|
+
// uses the real Soroban RPC through `createRpcServer`.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.createRpcServer = createRpcServer;
|
|
9
|
+
exports.probeNetwork = probeNetwork;
|
|
10
|
+
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
11
|
+
const PUBLIC_RPC_URLS = {
|
|
12
|
+
testnet: 'https://soroban-testnet.stellar.org',
|
|
13
|
+
// The brief pins testnet; mainnet is left to the caller via injection. We keep
|
|
14
|
+
// a public default that matches the brief's note ("e.g. https://mainnet.sorobanrpc.com").
|
|
15
|
+
mainnet: 'https://mainnet.sorobanrpc.com',
|
|
16
|
+
};
|
|
17
|
+
/** Build a fetcher backed by the public Soroban RPC for the given network.
|
|
18
|
+
* Injectable by tests via the `fetcher` parameter to `recordTransaction`. */
|
|
19
|
+
function createRpcServer(network) {
|
|
20
|
+
const server = new stellar_sdk_1.rpc.Server(PUBLIC_RPC_URLS[network], { allowHttp: false });
|
|
21
|
+
return async (hash) => {
|
|
22
|
+
const resp = await server.getTransaction(hash);
|
|
23
|
+
if (resp.status === stellar_sdk_1.rpc.Api.GetTransactionStatus.NOT_FOUND) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (resp.status === stellar_sdk_1.rpc.Api.GetTransactionStatus.SUCCESS) {
|
|
27
|
+
return {
|
|
28
|
+
status: 'SUCCESS',
|
|
29
|
+
ledger: resp.ledger,
|
|
30
|
+
createdAt: resp.createdAt,
|
|
31
|
+
txHash: resp.txHash,
|
|
32
|
+
envelopeXdr: resp.envelopeXdr,
|
|
33
|
+
resultXdr: resp.resultXdr,
|
|
34
|
+
resultMetaXdr: resp.resultMetaXdr,
|
|
35
|
+
events: resp.events,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
status: 'FAILED',
|
|
40
|
+
ledger: resp.ledger,
|
|
41
|
+
createdAt: resp.createdAt,
|
|
42
|
+
txHash: resp.txHash,
|
|
43
|
+
envelopeXdr: resp.envelopeXdr,
|
|
44
|
+
resultXdr: resp.resultXdr,
|
|
45
|
+
resultMetaXdr: resp.resultMetaXdr,
|
|
46
|
+
events: resp.events,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Lightweight reachability probe used by the integration test to decide whether
|
|
51
|
+
* to run or self-skip. Sends a JSON-RPC `getHealth` to the public endpoint. */
|
|
52
|
+
async function probeNetwork(network, timeoutMs = 3000) {
|
|
53
|
+
const url = PUBLIC_RPC_URLS[network];
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
56
|
+
try {
|
|
57
|
+
const resp = await fetch(url, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'getHealth' }),
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
});
|
|
63
|
+
if (!resp.ok)
|
|
64
|
+
return false;
|
|
65
|
+
const json = (await resp.json());
|
|
66
|
+
return json.result?.status === 'healthy';
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { OnChainEvent, TokenMovement } from '../types.ts';
|
|
2
|
+
export interface ValidationFailure {
|
|
3
|
+
code: 'MOVEMENT_PARSE_NOT_IN_EVENTS' | 'MOVEMENT_EVENT_NOT_PARSED';
|
|
4
|
+
message: string;
|
|
5
|
+
details: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** Cross-check parsed movements against the raw on-chain events.
|
|
8
|
+
* Returns null when validation passes; otherwise the first failure found.
|
|
9
|
+
*
|
|
10
|
+
* Mode:
|
|
11
|
+
* - on-chain mode (events.length > 0): full movement cross-check, fail-closed.
|
|
12
|
+
* - simulation/XDR mode (events.length === 0): SKIPPED - the caller surfaces
|
|
13
|
+
* the reduced certainty via parseConfidence.
|
|
14
|
+
*
|
|
15
|
+
* We intentionally do NOT require every invocation to have an attributable
|
|
16
|
+
* event. A Soroban call's events are commonly emitted by inner contracts
|
|
17
|
+
* (token SACs) that are not in the auth-derived invocation tree, so a
|
|
18
|
+
* per-invocation "did it emit an event" check produces false refusals on
|
|
19
|
+
* legitimate transactions. The real guards are the movement cross-check below
|
|
20
|
+
* plus parseConfidence (unknown contracts / opaque ScVals).
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateAgainstEvents(parsedMovements: TokenMovement[], events: OnChainEvent[]): ValidationFailure | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/record/validate.ts - cross-check parsed TokenMovements against the raw
|
|
3
|
+
// on-chain events. NOT by re-parsing.
|
|
4
|
+
//
|
|
5
|
+
// Why events-based validation matters: the parse path is the thing we are
|
|
6
|
+
// trying to harden. Validating parse output by re-running the parser can only
|
|
7
|
+
// catch a parser bug that manifests the same way twice. Cross-checking against
|
|
8
|
+
// the canonical event stream is what catches real drift.
|
|
9
|
+
//
|
|
10
|
+
// Skips:
|
|
11
|
+
// - simulation/XDR mode: there are no raw events to compare against. The
|
|
12
|
+
// orchestrator reflects the reduced certainty in parseConfidence (any
|
|
13
|
+
// unknown contract or opaque ScVal reduces overall below 1) and the gate
|
|
14
|
+
// refuses the recording.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.validateAgainstEvents = validateAgainstEvents;
|
|
17
|
+
const decode_ts_1 = require("./decode.js");
|
|
18
|
+
const movements_ts_1 = require("./movements.js");
|
|
19
|
+
/** Cross-check parsed movements against the raw on-chain events.
|
|
20
|
+
* Returns null when validation passes; otherwise the first failure found.
|
|
21
|
+
*
|
|
22
|
+
* Mode:
|
|
23
|
+
* - on-chain mode (events.length > 0): full movement cross-check, fail-closed.
|
|
24
|
+
* - simulation/XDR mode (events.length === 0): SKIPPED - the caller surfaces
|
|
25
|
+
* the reduced certainty via parseConfidence.
|
|
26
|
+
*
|
|
27
|
+
* We intentionally do NOT require every invocation to have an attributable
|
|
28
|
+
* event. A Soroban call's events are commonly emitted by inner contracts
|
|
29
|
+
* (token SACs) that are not in the auth-derived invocation tree, so a
|
|
30
|
+
* per-invocation "did it emit an event" check produces false refusals on
|
|
31
|
+
* legitimate transactions. The real guards are the movement cross-check below
|
|
32
|
+
* plus parseConfidence (unknown contracts / opaque ScVals).
|
|
33
|
+
*/
|
|
34
|
+
function validateAgainstEvents(parsedMovements, events) {
|
|
35
|
+
// Simulation/XDR mode - skip cross-check, return null (no failure).
|
|
36
|
+
if (events.length === 0)
|
|
37
|
+
return null;
|
|
38
|
+
// Compute the set of movements the raw events IMPLY.
|
|
39
|
+
const eventMovements = (0, movements_ts_1.extractTokenMovements)(events);
|
|
40
|
+
const eventKeySet = new Set(eventMovements.map(decode_ts_1.tokenMovementKey));
|
|
41
|
+
const parsedKeySet = new Set(parsedMovements.map(decode_ts_1.tokenMovementKey));
|
|
42
|
+
// 1. Every parsed movement must appear in the raw events.
|
|
43
|
+
for (const m of parsedMovements) {
|
|
44
|
+
if (!eventKeySet.has((0, decode_ts_1.tokenMovementKey)(m))) {
|
|
45
|
+
return {
|
|
46
|
+
code: 'MOVEMENT_PARSE_NOT_IN_EVENTS',
|
|
47
|
+
message: `parsed TokenMovement not present in raw events`,
|
|
48
|
+
details: { movement: m },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 2. Every raw-event movement must appear in the parsed set.
|
|
53
|
+
for (const m of eventMovements) {
|
|
54
|
+
if (!parsedKeySet.has((0, decode_ts_1.tokenMovementKey)(m))) {
|
|
55
|
+
return {
|
|
56
|
+
code: 'MOVEMENT_EVENT_NOT_PARSED',
|
|
57
|
+
message: `raw event implies a movement that the parse missed`,
|
|
58
|
+
details: { movement: m },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ScVal } from '../types.ts';
|
|
2
|
+
import type { ProtocolId } from './protocols.ts';
|
|
3
|
+
export interface IdentifiedProtocol {
|
|
4
|
+
protocol: ProtocolId;
|
|
5
|
+
fn: string;
|
|
6
|
+
}
|
|
7
|
+
/** Identify the protocol for a single (contract, method, args) invocation.
|
|
8
|
+
* Returns null on ANY mismatch (method not in ABI, arg count off, arg type
|
|
9
|
+
* off, or unknown method on a pinned address). The caller MUST keep the
|
|
10
|
+
* null path fail-closed. */
|
|
11
|
+
export declare function identifyProtocol(contract: string, method: string, args: ScVal[], network?: 'mainnet' | 'testnet'): IdentifiedProtocol | null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/registry/identify.ts - protocol identification (fail-closed).
|
|
3
|
+
//
|
|
4
|
+
// Given a (contract, method, args) triple, decide whether the call belongs
|
|
5
|
+
// to a known protocol. Returns null when it does NOT - the caller MUST
|
|
6
|
+
// preserve the fail-closed posture for null results.
|
|
7
|
+
//
|
|
8
|
+
// Recognition rules:
|
|
9
|
+
// 1. SEP-41: recognised by INTERFACE - any contract calling one of the
|
|
10
|
+
// SEP-41 fns (transfer / mint / burn / approve) with a matching arg
|
|
11
|
+
// shape is a SEP-41 token. No address pin is needed.
|
|
12
|
+
// 2. Blend: recognised by INTERFACE - real pool `submit` / `claim` calls
|
|
13
|
+
// hit per-pool instances, not the factory. The arg shape must match
|
|
14
|
+
// the Blend pool ABI (verified against the pool contract source).
|
|
15
|
+
// Address recognition still applies when the call is against the
|
|
16
|
+
// pinned factory address, with the same arg-shape check.
|
|
17
|
+
// 3. SoroSwap: recognised by ADDRESS only - the router is a single pinned
|
|
18
|
+
// contract. FIX 4: the method must ALSO be present in the protocol's
|
|
19
|
+
// ABI; an unknown method on a pinned router -> null (fail-closed).
|
|
20
|
+
//
|
|
21
|
+
// Method lookup uses `Object.hasOwn` rather than `in` so prototype-chain
|
|
22
|
+
// names like `constructor`, `toString`, `hasOwnProperty` do NOT register as
|
|
23
|
+
// valid method calls. Arg-shape validation compares arg count + each arg's
|
|
24
|
+
// ScVal subset type against the ABI signature; mismatch -> null.
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.identifyProtocol = identifyProtocol;
|
|
27
|
+
const known_addresses_ts_1 = require("./known-addresses.js");
|
|
28
|
+
const protocols_ts_1 = require("./protocols.js");
|
|
29
|
+
/** Identify the protocol for a single (contract, method, args) invocation.
|
|
30
|
+
* Returns null on ANY mismatch (method not in ABI, arg count off, arg type
|
|
31
|
+
* off, or unknown method on a pinned address). The caller MUST keep the
|
|
32
|
+
* null path fail-closed. */
|
|
33
|
+
function identifyProtocol(contract, method, args, network) {
|
|
34
|
+
// 1) SEP-41 by interface. Any contract whose (method, args) matches a
|
|
35
|
+
// SEP-41 ABI signature is a SEP-41 token.
|
|
36
|
+
if (Object.hasOwn(protocols_ts_1.SEP41_ABI, method)) {
|
|
37
|
+
const sig = protocols_ts_1.SEP41_ABI[method];
|
|
38
|
+
if (sig && argsMatchAbi(sig.args, args)) {
|
|
39
|
+
return { protocol: 'sep41', fn: method };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// 2) Blend by interface. Real pool calls hit per-pool instances; pinned
|
|
44
|
+
// factory calls also match here when the method is in the pool ABI.
|
|
45
|
+
if (Object.hasOwn(protocols_ts_1.BLEND_ABI, method)) {
|
|
46
|
+
const sig = protocols_ts_1.BLEND_ABI[method];
|
|
47
|
+
if (sig && argsMatchAbi(sig.args, args)) {
|
|
48
|
+
return { protocol: 'blend', fn: method };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
// 3) Pinned-address recognition (Blend factory + SoroSwap router/factory).
|
|
53
|
+
// FIX 4: the method must also be in the protocol's ABI; an unknown
|
|
54
|
+
// method on a pinned address -> null.
|
|
55
|
+
if (network) {
|
|
56
|
+
const pinned = (0, known_addresses_ts_1.addressToProtocol)(contract, network);
|
|
57
|
+
if (pinned) {
|
|
58
|
+
const abi = pinned === 'blend' ? protocols_ts_1.BLEND_ABI : protocols_ts_1.SOROSWAP_ABI;
|
|
59
|
+
if (Object.hasOwn(abi, method)) {
|
|
60
|
+
const sig = abi[method];
|
|
61
|
+
if (sig && argsMatchAbi(sig.args, args)) {
|
|
62
|
+
return { protocol: pinned, fn: method };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
void contract;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/** Compare the decoded args against the ABI signature. Returns true only when
|
|
71
|
+
* the arg count matches AND every arg's ScVal subset type matches the ABI's
|
|
72
|
+
* declared type. `other` is intentionally NOT a valid ABI match - it means
|
|
73
|
+
* the decoder couldn't classify the value, which is exactly the signal
|
|
74
|
+
* fail-closed should refuse. */
|
|
75
|
+
function argsMatchAbi(expected, actual) {
|
|
76
|
+
if (expected.length !== actual.length)
|
|
77
|
+
return false;
|
|
78
|
+
for (let i = 0; i < expected.length; i += 1) {
|
|
79
|
+
const want = expected[i];
|
|
80
|
+
const got = actual[i];
|
|
81
|
+
if (!want || !got)
|
|
82
|
+
return false;
|
|
83
|
+
if (want.type !== got.type)
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { type IdentifiedProtocol, identifyProtocol, } from './identify.ts';
|
|
2
|
+
export { addressToProtocol, KNOWN_ADDRESSES, type KnownAddressesByNetwork, type KnownAddressesByProtocol, } from './known-addresses.ts';
|
|
3
|
+
export { type AbiArg, type AbiEntry, BLEND_ABI, getAbi, PROTOCOL_ABIS, type ProtocolAbi, type ProtocolId, SEP41_ABI, SOROSWAP_ABI, } from './protocols.ts';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/registry/index.ts - re-export the protocol adjacency registry.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.SOROSWAP_ABI = exports.SEP41_ABI = exports.PROTOCOL_ABIS = exports.getAbi = exports.BLEND_ABI = exports.KNOWN_ADDRESSES = exports.addressToProtocol = exports.identifyProtocol = void 0;
|
|
5
|
+
var identify_ts_1 = require("./identify.js");
|
|
6
|
+
Object.defineProperty(exports, "identifyProtocol", { enumerable: true, get: function () { return identify_ts_1.identifyProtocol; } });
|
|
7
|
+
var known_addresses_ts_1 = require("./known-addresses.js");
|
|
8
|
+
Object.defineProperty(exports, "addressToProtocol", { enumerable: true, get: function () { return known_addresses_ts_1.addressToProtocol; } });
|
|
9
|
+
Object.defineProperty(exports, "KNOWN_ADDRESSES", { enumerable: true, get: function () { return known_addresses_ts_1.KNOWN_ADDRESSES; } });
|
|
10
|
+
var protocols_ts_1 = require("./protocols.js");
|
|
11
|
+
Object.defineProperty(exports, "BLEND_ABI", { enumerable: true, get: function () { return protocols_ts_1.BLEND_ABI; } });
|
|
12
|
+
Object.defineProperty(exports, "getAbi", { enumerable: true, get: function () { return protocols_ts_1.getAbi; } });
|
|
13
|
+
Object.defineProperty(exports, "PROTOCOL_ABIS", { enumerable: true, get: function () { return protocols_ts_1.PROTOCOL_ABIS; } });
|
|
14
|
+
Object.defineProperty(exports, "SEP41_ABI", { enumerable: true, get: function () { return protocols_ts_1.SEP41_ABI; } });
|
|
15
|
+
Object.defineProperty(exports, "SOROSWAP_ABI", { enumerable: true, get: function () { return protocols_ts_1.SOROSWAP_ABI; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ProtocolId } from './protocols.ts';
|
|
2
|
+
/** Pinned contract addresses per (network, protocol). Empty list = no
|
|
3
|
+
* addresses pinned for that cell. */
|
|
4
|
+
export interface KnownAddressesByProtocol {
|
|
5
|
+
blend: string[];
|
|
6
|
+
soroswap: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface KnownAddressesByNetwork {
|
|
9
|
+
mainnet: KnownAddressesByProtocol;
|
|
10
|
+
testnet: KnownAddressesByProtocol;
|
|
11
|
+
}
|
|
12
|
+
export declare const KNOWN_ADDRESSES: KnownAddressesByNetwork;
|
|
13
|
+
/** Map a (contract, network) pair to its pinned protocol id. Returns null
|
|
14
|
+
* when the address is not pinned for that network - callers MUST preserve
|
|
15
|
+
* the fail-closed posture for unpinned contracts. */
|
|
16
|
+
export declare function addressToProtocol(contract: string, network: 'mainnet' | 'testnet'): ProtocolId | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/registry/known-addresses.ts - pinned real protocol contract addresses.
|
|
3
|
+
//
|
|
4
|
+
// Addresses here are fetched from authoritative public sources:
|
|
5
|
+
// - Blend mainnet: docs.blend.capital/mainnet-deployments
|
|
6
|
+
// https://docs.blend.capital/mainnet-deployments
|
|
7
|
+
// - SoroSwap mainnet: github.com/soroswap (organisation README)
|
|
8
|
+
// https://github.com/soroswap
|
|
9
|
+
//
|
|
10
|
+
// Testnet addresses are intentionally NOT pinned: the public docs do not
|
|
11
|
+
// publish them, and per the task brief, unconfirmed addresses are marked
|
|
12
|
+
// [VERIFY] and excluded from the active set rather than invented.
|
|
13
|
+
//
|
|
14
|
+
// SEP-41 is recognised by interface (any token), so no address list is needed
|
|
15
|
+
// for it - see identify.ts.
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.KNOWN_ADDRESSES = void 0;
|
|
18
|
+
exports.addressToProtocol = addressToProtocol;
|
|
19
|
+
// Source: https://docs.blend.capital/mainnet-deployments (Blend Pool Factory
|
|
20
|
+
// mainnet deployment, fetched 2026-07-23).
|
|
21
|
+
const BLEND_MAINNET_POOL_FACTORY = 'CDSYOAVXFY7SM5S64IZPPPYB4GVGGLMQVFREPSQQEZVIWXX5R23G4QSU';
|
|
22
|
+
// Source: https://github.com/soroswap (organisation README, fetched 2026-07-23)
|
|
23
|
+
// SoroswapRouter mainnet deployment.
|
|
24
|
+
const SOROSWAP_MAINNET_ROUTER = 'CAG5LRYQ5JVEUI5TEID72EYOVX44TTUJT5BQR2J6J77FH65PCCFAJDDH';
|
|
25
|
+
// Source: https://github.com/soroswap (organisation README, fetched 2026-07-23)
|
|
26
|
+
// SoroswapFactory mainnet deployment.
|
|
27
|
+
const SOROSWAP_MAINNET_FACTORY = 'CA4HEQTL2WPEUYKYKCDOHCDNIV4QHNJ7EL4J4NQ6VADP7SYHVRYZ7AW2';
|
|
28
|
+
exports.KNOWN_ADDRESSES = {
|
|
29
|
+
mainnet: {
|
|
30
|
+
blend: [BLEND_MAINNET_POOL_FACTORY],
|
|
31
|
+
soroswap: [SOROSWAP_MAINNET_ROUTER, SOROSWAP_MAINNET_FACTORY],
|
|
32
|
+
},
|
|
33
|
+
testnet: {
|
|
34
|
+
// [VERIFY] Blend testnet pool factory address: not published in the
|
|
35
|
+
// public docs as of 2026-07-23. Excluded from the active set until an
|
|
36
|
+
// authoritative source is found.
|
|
37
|
+
blend: [],
|
|
38
|
+
// [VERIFY] SoroSwap testnet router / factory addresses: not published in
|
|
39
|
+
// the public docs as of 2026-07-23. Excluded from the active set.
|
|
40
|
+
soroswap: [],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
/** Map a (contract, network) pair to its pinned protocol id. Returns null
|
|
44
|
+
* when the address is not pinned for that network - callers MUST preserve
|
|
45
|
+
* the fail-closed posture for unpinned contracts. */
|
|
46
|
+
function addressToProtocol(contract, network) {
|
|
47
|
+
for (const protocol of ['blend', 'soroswap']) {
|
|
48
|
+
if (exports.KNOWN_ADDRESSES[network][protocol].includes(contract)) {
|
|
49
|
+
return protocol;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ScVal } from '../types.ts';
|
|
2
|
+
export type ProtocolId = 'sep41' | 'blend' | 'soroswap';
|
|
3
|
+
/** ScVal subset type vocabulary the ABI uses. Mirrors `ScVal['type']` minus
|
|
4
|
+
* `other` (which by definition cannot be matched against an ABI arg). */
|
|
5
|
+
export type AbiArgType = Exclude<ScVal['type'], 'other'>;
|
|
6
|
+
export interface AbiArg {
|
|
7
|
+
name: string;
|
|
8
|
+
/** ScVal subset type the decoded arg must conform to. */
|
|
9
|
+
type: AbiArgType;
|
|
10
|
+
/** Human-readable meaning for the downstream reviewer. */
|
|
11
|
+
meaning: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AbiEntry {
|
|
14
|
+
args: AbiArg[];
|
|
15
|
+
}
|
|
16
|
+
export type ProtocolAbi = Record<string, AbiEntry>;
|
|
17
|
+
/** SEP-41 token interface - pinned from the SEP-41 spec (any token). */
|
|
18
|
+
export declare const SEP41_ABI: ProtocolAbi;
|
|
19
|
+
/** Blend pool interface (v2). Pinning the two fns the recorder surfaces.
|
|
20
|
+
* Pool-level recognition is by INTERFACE (real pool calls hit per-pool
|
|
21
|
+
* instances, not the factory). Arg shapes verified against the Blend v2
|
|
22
|
+
* pool contract source (blend-capital/blend-contracts-v2 `pool/src/contract.rs`,
|
|
23
|
+
* fetched 2026-07-23):
|
|
24
|
+
* submit(env, from: Address, spender: Address, to: Address, requests: Vec<Request>)
|
|
25
|
+
* -> Positions
|
|
26
|
+
* claim(env, from: Address, reserve_token_ids: Vec<u32>, to: Address) -> i128
|
|
27
|
+
* Other pool fns (add_reserves, withdraw, etc.) are intentionally omitted -
|
|
28
|
+
* they are too version-sensitive to bind by interface. */
|
|
29
|
+
export declare const BLEND_ABI: ProtocolAbi;
|
|
30
|
+
/** SoroSwap router interface. Standard Uniswap-V2-style swap fn signatures
|
|
31
|
+
* pinned from the SoroSwap router source. Recognised by address only (the
|
|
32
|
+
* router is a single pinned contract); FIX 4 requires the method to also be
|
|
33
|
+
* in this ABI for address recognition to succeed. The `path` arg is a
|
|
34
|
+
* `vec<address>` - we record it as the outer `vec` subset and leave the
|
|
35
|
+
* per-element shape to the downstream reviewer. */
|
|
36
|
+
export declare const SOROSWAP_ABI: ProtocolAbi;
|
|
37
|
+
export declare const PROTOCOL_ABIS: Record<ProtocolId, ProtocolAbi>;
|
|
38
|
+
export declare function getAbi(protocol: ProtocolId): ProtocolAbi;
|