@stellaris-lab/por-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/README.md +77 -0
- package/dist/audit.d.ts +53 -0
- package/dist/audit.js +48 -0
- package/dist/backend.d.ts +70 -0
- package/dist/backend.js +91 -0
- package/dist/codec.d.ts +50 -0
- package/dist/codec.js +110 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.js +60 -0
- package/dist/domain.d.ts +199 -0
- package/dist/domain.js +57 -0
- package/dist/encoding.d.ts +67 -0
- package/dist/encoding.js +120 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +54 -0
- package/dist/events.d.ts +78 -0
- package/dist/events.js +66 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/manifest.d.ts +22 -0
- package/dist/manifest.js +54 -0
- package/dist/operations.d.ts +109 -0
- package/dist/operations.js +164 -0
- package/dist/persistence.d.ts +67 -0
- package/dist/persistence.js +154 -0
- package/dist/pipeline.d.ts +37 -0
- package/dist/pipeline.js +95 -0
- package/dist/policy.d.ts +27 -0
- package/dist/policy.js +60 -0
- package/dist/prove.d.ts +27 -0
- package/dist/prove.js +76 -0
- package/dist/reconciler.d.ts +65 -0
- package/dist/reconciler.js +152 -0
- package/dist/registry.d.ts +71 -0
- package/dist/registry.js +167 -0
- package/dist/signals.d.ts +36 -0
- package/dist/signals.js +194 -0
- package/dist/stellar.d.ts +129 -0
- package/dist/stellar.js +256 -0
- package/dist/transport.d.ts +70 -0
- package/dist/transport.js +94 -0
- package/package.json +50 -0
package/dist/stellar.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level Stellaris contract client.
|
|
3
|
+
*
|
|
4
|
+
* This module composes codec + transport + domain types. It does not contain
|
|
5
|
+
* UI or demo mocks. Integrators can supply a real SorobanTransport backed by
|
|
6
|
+
* @stellar/stellar-sdk, a server signer, or a test harness.
|
|
7
|
+
*/
|
|
8
|
+
import { bundleToContractArgs, decodeAttestation, decodeAttestationV2, decodeAttestationV3, } from "./codec.js";
|
|
9
|
+
import { StellarisError } from "./errors.js";
|
|
10
|
+
import { UnconfiguredSorobanTransport } from "./transport.js";
|
|
11
|
+
export var ContractErrorCode;
|
|
12
|
+
(function (ContractErrorCode) {
|
|
13
|
+
ContractErrorCode[ContractErrorCode["NotInitialized"] = 1] = "NotInitialized";
|
|
14
|
+
ContractErrorCode[ContractErrorCode["AlreadyInitialized"] = 2] = "AlreadyInitialized";
|
|
15
|
+
ContractErrorCode[ContractErrorCode["Unauthorized"] = 3] = "Unauthorized";
|
|
16
|
+
ContractErrorCode[ContractErrorCode["ProofInvalid"] = 4] = "ProofInvalid";
|
|
17
|
+
ContractErrorCode[ContractErrorCode["NotSolvent"] = 5] = "NotSolvent";
|
|
18
|
+
ContractErrorCode[ContractErrorCode["PeriodAlreadyAttested"] = 6] = "PeriodAlreadyAttested";
|
|
19
|
+
ContractErrorCode[ContractErrorCode["BadPublicSignals"] = 7] = "BadPublicSignals";
|
|
20
|
+
ContractErrorCode[ContractErrorCode["BadProofEncoding"] = 8] = "BadProofEncoding";
|
|
21
|
+
ContractErrorCode[ContractErrorCode["BadLiabilityRoot"] = 9] = "BadLiabilityRoot";
|
|
22
|
+
ContractErrorCode[ContractErrorCode["WrongVerifierVersion"] = 10] = "WrongVerifierVersion";
|
|
23
|
+
ContractErrorCode[ContractErrorCode["OracleMismatch"] = 11] = "OracleMismatch";
|
|
24
|
+
ContractErrorCode[ContractErrorCode["OracleNotConfigured"] = 12] = "OracleNotConfigured";
|
|
25
|
+
ContractErrorCode[ContractErrorCode["CustodianNotConfigured"] = 13] = "CustodianNotConfigured";
|
|
26
|
+
ContractErrorCode[ContractErrorCode["CustodianSigInvalid"] = 14] = "CustodianSigInvalid";
|
|
27
|
+
})(ContractErrorCode || (ContractErrorCode = {}));
|
|
28
|
+
const ERROR_MESSAGES = {
|
|
29
|
+
[ContractErrorCode.NotInitialized]: "Contract not initialized",
|
|
30
|
+
[ContractErrorCode.AlreadyInitialized]: "Contract already initialized",
|
|
31
|
+
[ContractErrorCode.Unauthorized]: "Issuer authorization missing",
|
|
32
|
+
[ContractErrorCode.ProofInvalid]: "Groth16 proof verification failed",
|
|
33
|
+
[ContractErrorCode.NotSolvent]: "Issuer is not solvent",
|
|
34
|
+
[ContractErrorCode.PeriodAlreadyAttested]: "Period already attested",
|
|
35
|
+
[ContractErrorCode.BadPublicSignals]: "Bad public signals",
|
|
36
|
+
[ContractErrorCode.BadProofEncoding]: "Bad proof encoding",
|
|
37
|
+
[ContractErrorCode.BadLiabilityRoot]: "Bad liability root",
|
|
38
|
+
[ContractErrorCode.WrongVerifierVersion]: "Unsupported verifier backend version",
|
|
39
|
+
[ContractErrorCode.OracleMismatch]: "Price commitment does not match the published oracle commitment",
|
|
40
|
+
[ContractErrorCode.OracleNotConfigured]: "No designated price oracle configured",
|
|
41
|
+
[ContractErrorCode.CustodianNotConfigured]: "No designated custodian configured",
|
|
42
|
+
[ContractErrorCode.CustodianSigInvalid]: "Custodian BLS signature verification failed",
|
|
43
|
+
};
|
|
44
|
+
export class ContractError extends Error {
|
|
45
|
+
code;
|
|
46
|
+
constructor(code) {
|
|
47
|
+
super(`[ContractError(${code})] ${ERROR_MESSAGES[code] ?? "Unknown contract error"}`);
|
|
48
|
+
this.name = "ContractError";
|
|
49
|
+
this.code = code;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class StellarisClient {
|
|
53
|
+
deployment;
|
|
54
|
+
transport;
|
|
55
|
+
constructor(options) {
|
|
56
|
+
this.deployment = options.deployment;
|
|
57
|
+
this.transport = options.transport ?? new UnconfiguredSorobanTransport();
|
|
58
|
+
}
|
|
59
|
+
async init(params) {
|
|
60
|
+
this.assertSigner(params.admin, params.signer);
|
|
61
|
+
const plan = this.plan("init", [params.admin, params.verificationKey]);
|
|
62
|
+
await this.transport.invoke(plan, params.signer);
|
|
63
|
+
}
|
|
64
|
+
async attest(params) {
|
|
65
|
+
this.assertSigner(params.issuer, params.signer);
|
|
66
|
+
// Validate bundle consistency (commitment vs. public signals) before submit.
|
|
67
|
+
// This throws on a malformed bundle without mutating anything.
|
|
68
|
+
bundleToContractArgs(params.bundle);
|
|
69
|
+
// Pass the raw snarkjs proof object + decimal public signals. The transport's
|
|
70
|
+
// contract codec serializes them to the on-chain byte layout through the
|
|
71
|
+
// single shared SDK encoder (encoding.ts). The decimal `{a,b,c}` tuple from
|
|
72
|
+
// bundleToContractArgs is a display/inspection shape only — not submitted.
|
|
73
|
+
const plan = this.plan("attest", [
|
|
74
|
+
params.issuer,
|
|
75
|
+
params.bundle.proof,
|
|
76
|
+
[...params.bundle.publicSignals],
|
|
77
|
+
]);
|
|
78
|
+
const result = await this.transport.invoke(plan, params.signer);
|
|
79
|
+
const attestation = decodeAttestation(result.value);
|
|
80
|
+
return {
|
|
81
|
+
attestation,
|
|
82
|
+
networkPassphrase: this.deployment.networkPassphrase,
|
|
83
|
+
contractId: this.deployment.contractId,
|
|
84
|
+
...(result.transactionHash === undefined ? {} : { transactionHash: result.transactionHash }),
|
|
85
|
+
...(result.ledger === undefined ? {} : { ledger: result.ledger }),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async getAttestation(issuer, periodId) {
|
|
89
|
+
const plan = this.plan("get_attestation", [issuer, periodId.toString()]);
|
|
90
|
+
const value = await this.transport.simulate(plan);
|
|
91
|
+
return value === null ? null : decodeAttestation(value);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* v2 attest: solvency with a SNARK-proven liability total. The proof bundle's
|
|
95
|
+
* public signals carry the circuit-computed `liabRoot`/`liabTotal`; the
|
|
96
|
+
* contract codec serializes the raw snarkjs proof via the shared encoder.
|
|
97
|
+
*/
|
|
98
|
+
async attestV2(params) {
|
|
99
|
+
this.assertSigner(params.issuer, params.signer);
|
|
100
|
+
const plan = this.plan("attest_v2", [
|
|
101
|
+
params.issuer,
|
|
102
|
+
params.bundle.proof,
|
|
103
|
+
[...params.bundle.publicSignals],
|
|
104
|
+
]);
|
|
105
|
+
const result = await this.transport.invoke(plan, params.signer);
|
|
106
|
+
const attestation = decodeAttestationV2(result.value);
|
|
107
|
+
return {
|
|
108
|
+
attestation,
|
|
109
|
+
networkPassphrase: this.deployment.networkPassphrase,
|
|
110
|
+
contractId: this.deployment.contractId,
|
|
111
|
+
...(result.transactionHash === undefined ? {} : { transactionHash: result.transactionHash }),
|
|
112
|
+
...(result.ledger === undefined ? {} : { ledger: result.ledger }),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async getAttestationV2(issuer, periodId) {
|
|
116
|
+
const plan = this.plan("get_attestation_v2", [issuer, periodId.toString()]);
|
|
117
|
+
const value = await this.transport.simulate(plan);
|
|
118
|
+
return value === null ? null : decodeAttestationV2(value);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* v3 attest: multi-asset solvency with an oracle-priced aggregate. The proof
|
|
122
|
+
* bundle's public signals carry the per-asset solvency flags + the aggregate
|
|
123
|
+
* flag + the reserve/price commitments; the contract requires the AGGREGATE to
|
|
124
|
+
* be solvent and stores the per-asset breakdown for transparency.
|
|
125
|
+
*/
|
|
126
|
+
async attestV3(params) {
|
|
127
|
+
this.assertSigner(params.issuer, params.signer);
|
|
128
|
+
const plan = this.plan("attest_v3", [
|
|
129
|
+
params.issuer,
|
|
130
|
+
params.bundle.proof,
|
|
131
|
+
[...params.bundle.publicSignals],
|
|
132
|
+
]);
|
|
133
|
+
const result = await this.transport.invoke(plan, params.signer);
|
|
134
|
+
const attestation = decodeAttestationV3(result.value);
|
|
135
|
+
return {
|
|
136
|
+
attestation,
|
|
137
|
+
networkPassphrase: this.deployment.networkPassphrase,
|
|
138
|
+
contractId: this.deployment.contractId,
|
|
139
|
+
...(result.transactionHash === undefined ? {} : { transactionHash: result.transactionHash }),
|
|
140
|
+
...(result.ledger === undefined ? {} : { ledger: result.ledger }),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async getAttestationV3(issuer, periodId) {
|
|
144
|
+
const plan = this.plan("get_attestation_v3", [issuer, periodId.toString()]);
|
|
145
|
+
const value = await this.transport.simulate(plan);
|
|
146
|
+
return value === null ? null : decodeAttestationV3(value);
|
|
147
|
+
}
|
|
148
|
+
async listPeriods(issuer) {
|
|
149
|
+
const plan = this.plan("list_periods", [issuer]);
|
|
150
|
+
const value = await this.transport.simulate(plan);
|
|
151
|
+
return value.map((period) => BigInt(String(period)));
|
|
152
|
+
}
|
|
153
|
+
async getVerificationKey() {
|
|
154
|
+
const plan = this.plan("get_vk", []);
|
|
155
|
+
return this.transport.simulate(plan);
|
|
156
|
+
}
|
|
157
|
+
async getAdmin() {
|
|
158
|
+
const plan = this.plan("get_admin", []);
|
|
159
|
+
const value = await this.transport.simulate(plan);
|
|
160
|
+
return value === null ? null : String(value);
|
|
161
|
+
}
|
|
162
|
+
// --- C3: designated price-oracle + per-period commitment binding ---------
|
|
163
|
+
/**
|
|
164
|
+
* Designate the price-oracle authority (admin-gated on-chain). `admin` must be
|
|
165
|
+
* the contract admin and must sign.
|
|
166
|
+
*/
|
|
167
|
+
async setOracle(params) {
|
|
168
|
+
this.assertSigner(params.admin, params.signer);
|
|
169
|
+
const plan = this.plan("set_oracle", [params.oracle]);
|
|
170
|
+
await this.transport.invoke(plan, params.signer);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Publish the authoritative price commitment for a period (oracle-gated
|
|
174
|
+
* on-chain). `oracle` must be the designated oracle and must sign. A later
|
|
175
|
+
* `attestV3` for the same period must present a matching `priceCommitment` or
|
|
176
|
+
* be rejected with `OracleMismatch`.
|
|
177
|
+
*/
|
|
178
|
+
async publishOracleCommitment(params) {
|
|
179
|
+
this.assertSigner(params.oracle, params.signer);
|
|
180
|
+
const plan = this.plan("publish_oracle_commitment", [
|
|
181
|
+
params.periodId.toString(),
|
|
182
|
+
params.commitment,
|
|
183
|
+
]);
|
|
184
|
+
await this.transport.invoke(plan, params.signer);
|
|
185
|
+
}
|
|
186
|
+
async getOracle() {
|
|
187
|
+
const plan = this.plan("get_oracle", []);
|
|
188
|
+
const value = await this.transport.simulate(plan);
|
|
189
|
+
return value === null ? null : String(value);
|
|
190
|
+
}
|
|
191
|
+
async getOracleCommitment(periodId) {
|
|
192
|
+
const plan = this.plan("get_oracle_commitment", [periodId.toString()]);
|
|
193
|
+
const value = await this.transport.simulate(plan);
|
|
194
|
+
return value === null || value === undefined ? null : String(value);
|
|
195
|
+
}
|
|
196
|
+
// --- C2: designated custodian + BLS-signed reserve attestation -----------
|
|
197
|
+
/**
|
|
198
|
+
* Designate the custodian BLS12-381 public key (G2), admin-gated on-chain.
|
|
199
|
+
* `pk` is the serialized G2 point (the transport/codec encodes it); `admin`
|
|
200
|
+
* must be the contract admin and must sign.
|
|
201
|
+
*/
|
|
202
|
+
async setCustodian(params) {
|
|
203
|
+
this.assertSigner(params.admin, params.signer);
|
|
204
|
+
const plan = this.plan("set_custodian", [params.custodianPublicKey]);
|
|
205
|
+
await this.transport.invoke(plan, params.signer);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Attest multi-asset solvency WITH a custodian BLS signature over the
|
|
209
|
+
* reserveCommitment. Like `attestV3` but additionally requires (and the
|
|
210
|
+
* contract verifies on-chain) a real BLS12-381 signature from the designated
|
|
211
|
+
* custodian; on success the attestation is stamped `custodianBound=true`.
|
|
212
|
+
* `custodianSig` is the serialized G1 signature point.
|
|
213
|
+
*/
|
|
214
|
+
async attestV3Signed(params) {
|
|
215
|
+
this.assertSigner(params.issuer, params.signer);
|
|
216
|
+
const plan = this.plan("attest_v3_signed", [
|
|
217
|
+
params.issuer,
|
|
218
|
+
params.bundle.proof,
|
|
219
|
+
[...params.bundle.publicSignals],
|
|
220
|
+
params.custodianSig,
|
|
221
|
+
]);
|
|
222
|
+
const result = await this.transport.invoke(plan, params.signer);
|
|
223
|
+
const attestation = decodeAttestationV3(result.value);
|
|
224
|
+
return {
|
|
225
|
+
attestation,
|
|
226
|
+
networkPassphrase: this.deployment.networkPassphrase,
|
|
227
|
+
contractId: this.deployment.contractId,
|
|
228
|
+
...(result.transactionHash === undefined ? {} : { transactionHash: result.transactionHash }),
|
|
229
|
+
...(result.ledger === undefined ? {} : { ledger: result.ledger }),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async getCustodian() {
|
|
233
|
+
const plan = this.plan("get_custodian", []);
|
|
234
|
+
const value = await this.transport.simulate(plan);
|
|
235
|
+
return value === null || value === undefined ? null : String(value);
|
|
236
|
+
}
|
|
237
|
+
plan(operation, args) {
|
|
238
|
+
return {
|
|
239
|
+
operation,
|
|
240
|
+
contractId: this.deployment.contractId,
|
|
241
|
+
args,
|
|
242
|
+
deployment: this.deployment,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
assertSigner(expectedPublicKey, signer) {
|
|
246
|
+
if (signer.publicKey !== expectedPublicKey) {
|
|
247
|
+
throw StellarisError.validation("signer public key does not match requested account", {
|
|
248
|
+
expectedPublicKey,
|
|
249
|
+
signerPublicKey: signer.publicKey,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export function createStellarisClient(options) {
|
|
255
|
+
return new StellarisClient(options);
|
|
256
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Transport abstractions for Soroban RPC and generated bindings. */
|
|
2
|
+
import { ContractDeployment, PublicKey } from "./domain.js";
|
|
3
|
+
import { ContractOperation } from "./operations.js";
|
|
4
|
+
export interface TransactionSigner {
|
|
5
|
+
readonly publicKey: PublicKey;
|
|
6
|
+
sign(transactionXdr: string, networkPassphrase: string): Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
export interface TransactionPlan<Name extends ContractOperation = ContractOperation> {
|
|
9
|
+
readonly operation: Name;
|
|
10
|
+
readonly contractId: string;
|
|
11
|
+
readonly args: readonly unknown[];
|
|
12
|
+
readonly deployment: ContractDeployment;
|
|
13
|
+
readonly idempotencyKey?: string;
|
|
14
|
+
readonly timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface TransactionResult<T> {
|
|
17
|
+
readonly value: T;
|
|
18
|
+
readonly transactionHash?: string;
|
|
19
|
+
readonly ledger?: bigint;
|
|
20
|
+
readonly envelopeXdr?: string;
|
|
21
|
+
readonly diagnosticEvents?: readonly unknown[];
|
|
22
|
+
}
|
|
23
|
+
export interface SorobanTransport {
|
|
24
|
+
invoke<T>(plan: TransactionPlan, signer?: TransactionSigner): Promise<TransactionResult<T>>;
|
|
25
|
+
simulate<T>(plan: TransactionPlan): Promise<T>;
|
|
26
|
+
}
|
|
27
|
+
export interface ContractInvocationRequest {
|
|
28
|
+
readonly contractId: string;
|
|
29
|
+
readonly operation: ContractOperation;
|
|
30
|
+
readonly args: readonly unknown[];
|
|
31
|
+
readonly deployment: ContractDeployment;
|
|
32
|
+
readonly signerPublicKey?: PublicKey;
|
|
33
|
+
readonly idempotencyKey?: string;
|
|
34
|
+
readonly timeoutMs?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ContractInvocationResponse<T = unknown> {
|
|
37
|
+
readonly value: T;
|
|
38
|
+
readonly transactionHash?: string;
|
|
39
|
+
readonly ledger?: bigint | number | string;
|
|
40
|
+
readonly envelopeXdr?: string;
|
|
41
|
+
readonly diagnosticEvents?: readonly unknown[];
|
|
42
|
+
}
|
|
43
|
+
export interface ContractInvoker {
|
|
44
|
+
invoke<T>(request: ContractInvocationRequest, signer?: TransactionSigner): Promise<ContractInvocationResponse<T>>;
|
|
45
|
+
simulate<T>(request: ContractInvocationRequest): Promise<T>;
|
|
46
|
+
}
|
|
47
|
+
export interface RetryingTransportOptions {
|
|
48
|
+
readonly invoker: ContractInvoker;
|
|
49
|
+
readonly maxAttempts?: number;
|
|
50
|
+
readonly baseDelayMs?: number;
|
|
51
|
+
readonly retryable?: (error: unknown, attempt: number, plan: TransactionPlan) => boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare class BindingSorobanTransport implements SorobanTransport {
|
|
54
|
+
private readonly invoker;
|
|
55
|
+
private readonly maxAttempts;
|
|
56
|
+
private readonly baseDelayMs;
|
|
57
|
+
private readonly retryable;
|
|
58
|
+
constructor(options: RetryingTransportOptions);
|
|
59
|
+
invoke<T>(plan: TransactionPlan, signer?: TransactionSigner): Promise<TransactionResult<T>>;
|
|
60
|
+
simulate<T>(plan: TransactionPlan): Promise<T>;
|
|
61
|
+
private validatePlan;
|
|
62
|
+
private toRequest;
|
|
63
|
+
private withRetry;
|
|
64
|
+
}
|
|
65
|
+
export declare class UnconfiguredSorobanTransport implements SorobanTransport {
|
|
66
|
+
invoke<T>(plan: TransactionPlan, _signer?: TransactionSigner): Promise<TransactionResult<T>>;
|
|
67
|
+
simulate<T>(plan: TransactionPlan): Promise<T>;
|
|
68
|
+
}
|
|
69
|
+
export declare function normalizeTransactionResult<T>(response: ContractInvocationResponse<T>): TransactionResult<T>;
|
|
70
|
+
export declare function defaultRetryable(error: unknown, _attempt: number, _plan: TransactionPlan): boolean;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Transport abstractions for Soroban RPC and generated bindings. */
|
|
2
|
+
import { StellarisError, wrapUnknown } from "./errors.js";
|
|
3
|
+
import { assertOperationArgs, getOperationSpec, isReadOperation } from "./operations.js";
|
|
4
|
+
export class BindingSorobanTransport {
|
|
5
|
+
invoker;
|
|
6
|
+
maxAttempts;
|
|
7
|
+
baseDelayMs;
|
|
8
|
+
retryable;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.invoker = options.invoker;
|
|
11
|
+
this.maxAttempts = options.maxAttempts ?? 3;
|
|
12
|
+
this.baseDelayMs = options.baseDelayMs ?? 250;
|
|
13
|
+
this.retryable = options.retryable ?? defaultRetryable;
|
|
14
|
+
}
|
|
15
|
+
async invoke(plan, signer) {
|
|
16
|
+
this.validatePlan(plan, "write");
|
|
17
|
+
const request = this.toRequest(plan, signer?.publicKey);
|
|
18
|
+
const response = await this.withRetry(plan, () => this.invoker.invoke(request, signer));
|
|
19
|
+
return normalizeTransactionResult(response);
|
|
20
|
+
}
|
|
21
|
+
async simulate(plan) {
|
|
22
|
+
this.validatePlan(plan, "read");
|
|
23
|
+
const request = this.toRequest(plan);
|
|
24
|
+
return this.withRetry(plan, () => this.invoker.simulate(request));
|
|
25
|
+
}
|
|
26
|
+
validatePlan(plan, mode) {
|
|
27
|
+
assertOperationArgs(plan.operation, plan.args);
|
|
28
|
+
const spec = getOperationSpec(plan.operation);
|
|
29
|
+
if (mode === "read" && !isReadOperation(plan.operation)) {
|
|
30
|
+
throw StellarisError.transport(`cannot simulate mutating operation ${plan.operation}`);
|
|
31
|
+
}
|
|
32
|
+
if (mode === "write" && spec.mutability !== "write") {
|
|
33
|
+
throw StellarisError.transport(`cannot invoke read-only operation ${plan.operation}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
toRequest(plan, signerPublicKey) {
|
|
37
|
+
return {
|
|
38
|
+
contractId: plan.contractId,
|
|
39
|
+
operation: plan.operation,
|
|
40
|
+
args: plan.args,
|
|
41
|
+
deployment: plan.deployment,
|
|
42
|
+
...(signerPublicKey === undefined ? {} : { signerPublicKey }),
|
|
43
|
+
...(plan.idempotencyKey === undefined ? {} : { idempotencyKey: plan.idempotencyKey }),
|
|
44
|
+
...(plan.timeoutMs === undefined ? {} : { timeoutMs: plan.timeoutMs }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async withRetry(plan, action) {
|
|
48
|
+
let lastError;
|
|
49
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
50
|
+
try {
|
|
51
|
+
return await action();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
lastError = error;
|
|
55
|
+
if (attempt >= this.maxAttempts || !this.retryable(error, attempt, plan)) {
|
|
56
|
+
throw wrapUnknown("transport", `transport failed for ${plan.operation}`, error);
|
|
57
|
+
}
|
|
58
|
+
await delay(this.baseDelayMs * 2 ** (attempt - 1));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw wrapUnknown("transport", `transport failed for ${plan.operation}`, lastError);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class UnconfiguredSorobanTransport {
|
|
65
|
+
async invoke(plan, _signer) {
|
|
66
|
+
throw StellarisError.transport(`no Soroban transport configured for ${plan.operation}`, {
|
|
67
|
+
context: { operation: plan.operation, contractId: plan.contractId },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async simulate(plan) {
|
|
71
|
+
throw StellarisError.transport(`no Soroban transport configured for ${plan.operation}`, {
|
|
72
|
+
context: { operation: plan.operation, contractId: plan.contractId },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function normalizeTransactionResult(response) {
|
|
77
|
+
return {
|
|
78
|
+
value: response.value,
|
|
79
|
+
...(response.transactionHash === undefined ? {} : { transactionHash: response.transactionHash }),
|
|
80
|
+
...(response.ledger === undefined ? {} : { ledger: BigInt(String(response.ledger)) }),
|
|
81
|
+
...(response.envelopeXdr === undefined ? {} : { envelopeXdr: response.envelopeXdr }),
|
|
82
|
+
...(response.diagnosticEvents === undefined ? {} : { diagnosticEvents: response.diagnosticEvents }),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function defaultRetryable(error, _attempt, _plan) {
|
|
86
|
+
if (error instanceof StellarisError) {
|
|
87
|
+
return error.kind === "transport";
|
|
88
|
+
}
|
|
89
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
90
|
+
return message.includes("timeout") || message.includes("rate") || message.includes("temporar");
|
|
91
|
+
}
|
|
92
|
+
function delay(ms) {
|
|
93
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
94
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stellaris-lab/por-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Stellaris proof-of-reserves SDK: circuits, proving, contract encoding, and Soroban client abstractions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"package.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"test": "npm run build && node --test \"test/*.test.ts\"",
|
|
23
|
+
"check": "npm run lint && npm test",
|
|
24
|
+
"prepack": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"stellar",
|
|
31
|
+
"soroban",
|
|
32
|
+
"proof-of-reserves",
|
|
33
|
+
"rwa",
|
|
34
|
+
"stablecoin",
|
|
35
|
+
"zk",
|
|
36
|
+
"groth16"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT OR Apache-2.0",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@stellar/stellar-sdk": "^13.1.0",
|
|
41
|
+
"snarkjs": "^0.7.5"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.11.0",
|
|
45
|
+
"typescript": "^5.5.0"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|