Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions @stellar/typescript-wallet-sdk/src/walletSdk/Types/sep7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum Sep7OperationType {
}

export const URI_MSG_MAX_LENGTH = 300;
export const URI_REPLACE_MAX_LENGTH = 4096;

export type Sep7Replacement = {
id: string;
Expand Down
52 changes: 46 additions & 6 deletions @stellar/typescript-wallet-sdk/src/walletSdk/Uri/sep7Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IsValidSep7UriResult,
WEB_STELLAR_SCHEME,
URI_MSG_MAX_LENGTH,
URI_REPLACE_MAX_LENGTH,
} from "../Types";
import {
Sep7InvalidUriError,
Expand Down Expand Up @@ -162,16 +163,55 @@ export const sep7ReplacementsFromString = (
return [];
}

if (replacements.length > URI_REPLACE_MAX_LENGTH) {
throw new Sep7InvalidUriError(
"the 'replace' parameter exceeds the maximum allowed length",
);
}

const [txrepString, hintsString] = replacements.split(HINT_DELIMITER);
const hintsList = hintsString.split(LIST_DELIMITER);

const hintsMap: { [id: string]: string } = {};
const txrepList = txrepString.split(LIST_DELIMITER);
const txrepIds: string[] = [];
txrepList.forEach((item) => {
const parts = item.split(ID_DELIMITER);
if (parts.length < 2 || !parts[0] || !parts[1]) {
throw new Sep7InvalidUriError(
"the 'replace' parameter has an entry missing a path or reference identifier",
);
}
const id = parts[1];
if (txrepIds.indexOf(id) === -1) {
txrepIds.push(id);
}
});

hintsList
.map((item) => item.split(ID_DELIMITER))
.forEach(([id, hint]) => (hintsMap[id] = hint));
const hintsMap = Object.create(null) as Record<string, string>;

const txrepList = txrepString.split(LIST_DELIMITER);
if (hintsString) {
const hintsList = hintsString.split(LIST_DELIMITER);
hintsList.forEach((item) => {
const parts = item.split(ID_DELIMITER);
if (parts.length < 2 || !parts[0] || !parts[1]) {
throw new Sep7InvalidUriError(
"the 'replace' parameter has a hint entry missing an identifier or hint value",
);
}
hintsMap[parts[0]] = parts[1];
});
}

const hintIds = Object.keys(hintsMap);

const isBalanced =
txrepIds.length === hintIds.length &&
txrepIds.every((id) => Object.prototype.hasOwnProperty.call(hintsMap, id));

if (!isBalanced) {
throw new Sep7InvalidUriError(
"the 'replace' parameter has unbalanced reference identifiers",
);
}

const replacementsList = txrepList
.map((item) => item.split(ID_DELIMITER))
Expand Down
29 changes: 29 additions & 0 deletions @stellar/typescript-wallet-sdk/test/sep7.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,35 @@ describe("sep7Parser", () => {
);
});

it("sep7ReplacementsFromString() throws on replace string without hints (no ';' delimiter)", () => {
const str = "sourceAccount:X,operations[0].sourceAccount:Y";
expect(() => sep7ReplacementsFromString(str)).toThrow(Sep7InvalidUriError);
});

it("sep7ReplacementsFromString() throws on single replacement without hints", () => {
const str = "sourceAccount:X";
expect(() => sep7ReplacementsFromString(str)).toThrow(Sep7InvalidUriError);
});

it("sep7ReplacementsFromString() throws on unbalanced identifiers", () => {
// Spec example: {X} on left but {Y} on right should be rejected
const str = "sourceAccount:X;Y:The account";
expect(() => sep7ReplacementsFromString(str)).toThrow(Sep7InvalidUriError);
});

it("sep7ReplacementsFromString() returns empty array for undefined/empty input", () => {
expect(sep7ReplacementsFromString(undefined)).toEqual([]);
expect(sep7ReplacementsFromString("")).toEqual([]);
});

it("Sep7Tx.getReplacements() throws on replace param missing hints", () => {
const uri = new Sep7Tx(
`web+stellar:tx?xdr=test&replace=${encodeURIComponent("sourceAccount:X")}`,
);

expect(() => uri.getReplacements()).toThrow(Sep7InvalidUriError);
});

it("sep7ReplacementsToString outputs the right string", () => {
const expected =
"sourceAccount:X,operations[0].sourceAccount:Y,operations[1].destination:Y;X:account from where you want to pay fees,Y:account that needs the trustline and which will receive the new tokens";
Expand Down
Loading