@openzeppelin/relayer-plugin-channels 0.2.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +504 -0
  3. package/dist/build.d.ts +21 -0
  4. package/dist/build.d.ts.map +1 -0
  5. package/dist/build.js +100 -0
  6. package/dist/config.d.ts +31 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +89 -0
  9. package/dist/constants.d.ts +46 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/constants.js +56 -0
  12. package/dist/fee.d.ts +11 -0
  13. package/dist/fee.d.ts.map +1 -0
  14. package/dist/fee.js +41 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +115 -0
  18. package/dist/management.d.ts +11 -0
  19. package/dist/management.d.ts.map +1 -0
  20. package/dist/management.js +166 -0
  21. package/dist/pool.d.ts +31 -0
  22. package/dist/pool.d.ts.map +1 -0
  23. package/dist/pool.js +129 -0
  24. package/dist/simulation.d.ts +14 -0
  25. package/dist/simulation.d.ts.map +1 -0
  26. package/dist/simulation.js +52 -0
  27. package/dist/submit.d.ts +20 -0
  28. package/dist/submit.d.ts.map +1 -0
  29. package/dist/submit.js +102 -0
  30. package/dist/test/config.test.d.ts +2 -0
  31. package/dist/test/config.test.d.ts.map +1 -0
  32. package/dist/test/config.test.js +43 -0
  33. package/dist/test/fee.test.d.ts +2 -0
  34. package/dist/test/fee.test.d.ts.map +1 -0
  35. package/dist/test/fee.test.js +34 -0
  36. package/dist/test/helpers/fakeKV.d.ts +17 -0
  37. package/dist/test/helpers/fakeKV.d.ts.map +1 -0
  38. package/dist/test/helpers/fakeKV.js +60 -0
  39. package/dist/test/management.test.d.ts +2 -0
  40. package/dist/test/management.test.d.ts.map +1 -0
  41. package/dist/test/management.test.js +66 -0
  42. package/dist/test/pool.busy.test.d.ts +2 -0
  43. package/dist/test/pool.busy.test.d.ts.map +1 -0
  44. package/dist/test/pool.busy.test.js +23 -0
  45. package/dist/test/pool.test.d.ts +2 -0
  46. package/dist/test/pool.test.d.ts.map +1 -0
  47. package/dist/test/pool.test.js +29 -0
  48. package/dist/test/tx.test.d.ts +2 -0
  49. package/dist/test/tx.test.d.ts.map +1 -0
  50. package/dist/test/tx.test.js +29 -0
  51. package/dist/test/validation.test.d.ts +2 -0
  52. package/dist/test/validation.test.d.ts.map +1 -0
  53. package/dist/test/validation.test.js +31 -0
  54. package/dist/tx.d.ts +8 -0
  55. package/dist/tx.d.ts.map +1 -0
  56. package/dist/tx.js +45 -0
  57. package/dist/types.d.ts +66 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +7 -0
  60. package/dist/validation.d.ts +9 -0
  61. package/dist/validation.d.ts.map +1 -0
  62. package/dist/validation.js +84 -0
  63. package/package.json +63 -0
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
5
+ const tx_1 = require("../tx");
6
+ (0, vitest_1.describe)("tx validation", () => {
7
+ const passphrase = stellar_sdk_1.Networks.TESTNET;
8
+ (0, vitest_1.test)("throws on far future timebounds", () => {
9
+ const acc = new stellar_sdk_1.Account("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", "1");
10
+ const tx = new stellar_sdk_1.TransactionBuilder(acc, {
11
+ fee: "100",
12
+ networkPassphrase: passphrase,
13
+ })
14
+ .setTimeout(300)
15
+ .build();
16
+ (0, vitest_1.expect)(() => (0, tx_1.validateExistingTransactionForSubmitOnly)(tx)).toThrow("too far into the future");
17
+ });
18
+ (0, vitest_1.test)("passes on short timeout", () => {
19
+ const acc = new stellar_sdk_1.Account("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", "1");
20
+ const tx = new stellar_sdk_1.TransactionBuilder(acc, {
21
+ fee: "100",
22
+ networkPassphrase: passphrase,
23
+ })
24
+ .setTimeout(30)
25
+ .build();
26
+ const out = (0, tx_1.validateExistingTransactionForSubmitOnly)(tx);
27
+ (0, vitest_1.expect)(out).toBe(tx);
28
+ });
29
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.test.d.ts","sourceRoot":"","sources":["../../src/test/validation.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
5
+ const validation_1 = require("../validation");
6
+ (0, vitest_1.describe)("validation", () => {
7
+ (0, vitest_1.test)("accepts xdr-only request", () => {
8
+ const out = (0, validation_1.validateAndParseRequest)({ xdr: "BASE64XDR" });
9
+ (0, vitest_1.expect)(out).toEqual({ type: "xdr", xdr: "BASE64XDR" });
10
+ });
11
+ (0, vitest_1.test)("rejects xdr with extra keys", () => {
12
+ (0, vitest_1.expect)(() => (0, validation_1.validateAndParseRequest)({ xdr: "X", extra: 1 })).toThrow("`xdr` request must not include other parameters");
13
+ });
14
+ (0, vitest_1.test)("accepts func+auth with valid base64", () => {
15
+ const contract = new stellar_sdk_1.Contract("CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC");
16
+ const op = contract.call("no_auth_bump", stellar_sdk_1.xdr.ScVal.scvU32(1));
17
+ const body = op.body();
18
+ const inv = body.invokeHostFunctionOp();
19
+ const func = inv.hostFunction().toXDR("base64");
20
+ const auth = (inv.auth() ?? []).map((a) => a.toXDR("base64"));
21
+ const out = (0, validation_1.validateAndParseRequest)({ func, auth });
22
+ (0, vitest_1.expect)(out.type).toBe("func-auth");
23
+ });
24
+ (0, vitest_1.test)("rejects missing both", () => {
25
+ (0, vitest_1.expect)(() => (0, validation_1.validateAndParseRequest)({})).toThrow("Must pass either `xdr` or `func` and `auth`");
26
+ });
27
+ (0, vitest_1.test)("rejects missing func or missing auth", () => {
28
+ (0, vitest_1.expect)(() => (0, validation_1.validateAndParseRequest)({ func: "AAAA" })).toThrow("`func` and `auth` are both required");
29
+ (0, vitest_1.expect)(() => (0, validation_1.validateAndParseRequest)({ auth: [] })).toThrow("`func` and `auth` are both required");
30
+ });
31
+ });
package/dist/tx.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * tx.ts
3
+ *
4
+ * Transaction validation helpers for the XDR submit-only path.
5
+ */
6
+ import { Transaction } from "@stellar/stellar-sdk";
7
+ export declare function validateExistingTransactionForSubmitOnly(tx: Transaction): Transaction;
8
+ //# sourceMappingURL=tx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tx.d.ts","sourceRoot":"","sources":["../src/tx.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAIxD,wBAAgB,wCAAwC,CACtD,EAAE,EAAE,WAAW,GACd,WAAW,CA6Cb"}
package/dist/tx.js ADDED
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * tx.ts
4
+ *
5
+ * Transaction validation helpers for the XDR submit-only path.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.validateExistingTransactionForSubmitOnly = validateExistingTransactionForSubmitOnly;
9
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
10
+ const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
11
+ const constants_1 = require("./constants");
12
+ function validateExistingTransactionForSubmitOnly(tx) {
13
+ const now = Math.floor(Date.now() / 1000);
14
+ // Reject fee-bump envelopes
15
+ const envelope = tx.toEnvelope();
16
+ const kind = envelope.switch();
17
+ if (kind !== stellar_sdk_1.xdr.EnvelopeType.envelopeTypeTx()) {
18
+ throw (0, relayer_sdk_1.pluginError)("Input must be a regular transaction envelope (fee-bump not allowed)", {
19
+ code: "INVALID_ENVELOPE_TYPE",
20
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
21
+ });
22
+ }
23
+ // Soroban sanity checks
24
+ const sorobanData = envelope.v1().tx().ext().sorobanData();
25
+ if (sorobanData) {
26
+ const resourceFee = sorobanData.resourceFee().toBigInt();
27
+ if (BigInt(tx.fee) > resourceFee + 201n) {
28
+ throw (0, relayer_sdk_1.pluginError)("Transaction fee must be equal to the resource fee", {
29
+ code: "FEE_MISMATCH",
30
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
31
+ details: { fee: tx.fee, resourceFee: resourceFee.toString() },
32
+ });
33
+ }
34
+ }
35
+ // Time bounds sanity
36
+ if (tx.timeBounds?.maxTime &&
37
+ Number(tx.timeBounds.maxTime) - now >
38
+ constants_1.SIMULATION.MAX_FUTURE_TIME_BOUND_SECONDS) {
39
+ throw (0, relayer_sdk_1.pluginError)(`Transaction \`timeBounds.maxTime\` too far into the future. Must be no greater than ${constants_1.SIMULATION.MAX_FUTURE_TIME_BOUND_SECONDS} seconds`, {
40
+ code: "TIMEBOUNDS_TOO_FAR",
41
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
42
+ });
43
+ }
44
+ return tx;
45
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * types.ts
3
+ *
4
+ * Type definitions for the channel accounts plugin.
5
+ */
6
+ /**
7
+ * Plugin request parameters:
8
+ * - Submit-only: signed regular transaction XDR
9
+ * - Channel build: func + auth (always simulated)
10
+ */
11
+ import type { xdr } from "@stellar/stellar-sdk";
12
+ export type ChannelAccountsRequest = {
13
+ type: "xdr";
14
+ xdr: string;
15
+ } | {
16
+ type: "func-auth";
17
+ func: xdr.HostFunction;
18
+ auth: xdr.SorobanAuthorizationEntry[];
19
+ };
20
+ /**
21
+ * Plugin response format aligned with launchtube
22
+ */
23
+ export interface ChannelAccountsResponse {
24
+ transactionId: string | null;
25
+ status: string | null;
26
+ hash: string | null;
27
+ }
28
+ /**
29
+ * Management request structure
30
+ */
31
+ export interface ManagementRequest {
32
+ management: {
33
+ adminSecret: string;
34
+ action: "listChannelAccounts" | "setChannelAccounts";
35
+ relayerIds?: string[];
36
+ };
37
+ }
38
+ /**
39
+ * Management response for listing channel accounts
40
+ */
41
+ export interface ListChannelAccountsResponse {
42
+ relayerIds: string[];
43
+ }
44
+ /**
45
+ * Management response for setting channel accounts
46
+ */
47
+ export interface SetChannelAccountsResponse {
48
+ ok: boolean;
49
+ appliedRelayerIds: string[];
50
+ }
51
+ /**
52
+ * Channel account info
53
+ */
54
+ export interface ChannelAccountInfo {
55
+ relayerId: string;
56
+ address: string;
57
+ sequenceNumber: string;
58
+ }
59
+ /**
60
+ * Fund account info
61
+ */
62
+ export interface FundAccountInfo {
63
+ relayerId: string;
64
+ address: string;
65
+ }
66
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5B;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC;IACvB,IAAI,EAAE,GAAG,CAAC,yBAAyB,EAAE,CAAC;CACvC,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;QACrD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,OAAO,CAAC;IACZ,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * types.ts
4
+ *
5
+ * Type definitions for the channel accounts plugin.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * validation.ts
3
+ *
4
+ * Request validation and parsing for the Channel Accounts plugin.
5
+ * Supports either a signed XDR or func+auth.
6
+ */
7
+ import { ChannelAccountsRequest } from "./types";
8
+ export declare function validateAndParseRequest(params: any): ChannelAccountsRequest;
9
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEjD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,GAAG,GAAG,sBAAsB,CAyF3E"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ /**
3
+ * validation.ts
4
+ *
5
+ * Request validation and parsing for the Channel Accounts plugin.
6
+ * Supports either a signed XDR or func+auth.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.validateAndParseRequest = validateAndParseRequest;
10
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
11
+ const relayer_sdk_1 = require("@openzeppelin/relayer-sdk");
12
+ const constants_1 = require("./constants");
13
+ function validateAndParseRequest(params) {
14
+ if (!params || typeof params !== "object") {
15
+ throw (0, relayer_sdk_1.pluginError)("Invalid request: params must be an object", {
16
+ code: "INVALID_PARAMS",
17
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
18
+ });
19
+ }
20
+ // Disallow any management-shaped keys leaking into here; management is handled earlier
21
+ const keys = Object.keys(params);
22
+ // Mode: XDR
23
+ if ("xdr" in params) {
24
+ if (typeof params.xdr !== "string" || params.xdr.trim() === "") {
25
+ throw (0, relayer_sdk_1.pluginError)("`xdr` must be a non-empty base64 string", {
26
+ code: "INVALID_PARAMS",
27
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
28
+ });
29
+ }
30
+ // Strict: cannot include func/auth when using xdr
31
+ const unknown = keys.filter((k) => !["xdr"].includes(k));
32
+ if (unknown.length > 0) {
33
+ throw (0, relayer_sdk_1.pluginError)("`xdr` request must not include other parameters", {
34
+ code: "INVALID_PARAMS",
35
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
36
+ details: { unknown },
37
+ });
38
+ }
39
+ return { type: "xdr", xdr: params.xdr.trim() };
40
+ }
41
+ // Mode: func+auth
42
+ if ("func" in params || "auth" in params) {
43
+ if (!params.func || !params.auth) {
44
+ throw (0, relayer_sdk_1.pluginError)("`func` and `auth` are both required when omitting `xdr`", {
45
+ code: "INVALID_PARAMS",
46
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
47
+ });
48
+ }
49
+ // Parse values from base64
50
+ let func;
51
+ let auth = [];
52
+ try {
53
+ func = stellar_sdk_1.xdr.HostFunction.fromXDR(params.func, "base64");
54
+ if (!Array.isArray(params.auth)) {
55
+ throw new Error("auth must be an array of base64 strings");
56
+ }
57
+ auth = params.auth.map((a) => stellar_sdk_1.xdr.SorobanAuthorizationEntry.fromXDR(a, "base64"));
58
+ }
59
+ catch (e) {
60
+ throw (0, relayer_sdk_1.pluginError)("Invalid `func` or `auth` encoding", {
61
+ code: "INVALID_PARAMS",
62
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
63
+ details: { message: e instanceof Error ? e.message : String(e) },
64
+ });
65
+ }
66
+ // Reject SourceAccount credentials: incompatible with relayer-managed channel source
67
+ for (const entry of auth) {
68
+ const credType = entry.credentials().switch();
69
+ if (credType ===
70
+ stellar_sdk_1.xdr.SorobanCredentialsType.sorobanCredentialsSourceAccount()) {
71
+ throw (0, relayer_sdk_1.pluginError)("Detached address credentials required: source-account credentials are incompatible with relayer-managed channel accounts", {
72
+ code: "INVALID_PARAMS",
73
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
74
+ details: { reason: "source-account credentials not allowed" },
75
+ });
76
+ }
77
+ }
78
+ return { type: "func-auth", func, auth };
79
+ }
80
+ throw (0, relayer_sdk_1.pluginError)("Must pass either `xdr` or `func` and `auth`", {
81
+ code: "INVALID_PARAMS",
82
+ status: constants_1.HTTP_STATUS.BAD_REQUEST,
83
+ });
84
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@openzeppelin/relayer-plugin-channels",
3
+ "version": "0.2.0",
4
+ "description": "OpenZeppelin Relayer Plugin for Stellar Channel Accounts",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "package.json"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/OpenZeppelin/relayer-plugin-channels.git"
18
+ },
19
+ "author": "OpenZeppelin <contact@openzeppelin.com>",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@openzeppelin/relayer-sdk": "^1.6.0",
23
+ "@stellar/stellar-sdk": "^11.2.0",
24
+ "@actions/exec": "^1.1.1"
25
+ },
26
+ "devDependencies": {
27
+ "@changesets/cli": "^2.27.7",
28
+ "@types/node": "^22.13.14",
29
+ "@typescript-eslint/eslint-plugin": "^8.38.0",
30
+ "@typescript-eslint/parser": "^8.38.0",
31
+ "eslint": "^9.22.0",
32
+ "eslint-config-prettier": "^9.0.0",
33
+ "eslint-plugin-prettier": "^5.0.0",
34
+ "prettier": "^3.0.0",
35
+ "typescript": "5.8.3",
36
+ "typescript-eslint": "8.27.0",
37
+ "vitest": "^4.0.4"
38
+ },
39
+ "engines": {
40
+ "node": ">=22.18.0",
41
+ "npm": "use pnpm",
42
+ "pnpm": ">=9",
43
+ "yarn": "use pnpm"
44
+ },
45
+ "scripts": {
46
+ "dev": "tsc --watch",
47
+ "build": "tsc",
48
+ "lint": "eslint . --fix",
49
+ "lint:check": "eslint .",
50
+ "lint:fix": "pnpm prettier:fix && pnpm lint:check && pnpm prettier:check",
51
+ "prettier:check": "prettier -u --check '**/*.{js,ts,tsx,md}'",
52
+ "prettier:fix": "prettier -u --write '**/*.{js,ts,tsx,md}'",
53
+ "style": "pnpm lint:fix",
54
+ "format": "prettier --write .",
55
+ "format:check": "prettier --check .",
56
+ "test": "vitest run",
57
+ "clean": "rm -rf dist",
58
+ "install-deps": "pnpm install --frozen-lockfile --ignore-scripts --prefer-offline",
59
+ "install-pnpm": "npm install -g pnpm",
60
+ "ci:version": "pnpm changeset version",
61
+ "release": "node ./scripts/release.js"
62
+ }
63
+ }