-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
24aeafa
commit 43622ad
Showing
6 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { decodePayment } from "./payment.utils"; | ||
|
||
describe("payment.utils", () => { | ||
it("extract payment information", () => { | ||
expect( | ||
decodePayment( | ||
"bitcoin:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?amount=0.00001" | ||
) | ||
).toEqual({ | ||
token: "bitcoin", | ||
identifier: "BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U", | ||
amount: 0.00001, | ||
}); | ||
|
||
expect( | ||
decodePayment( | ||
"icp:646f4d2d6fcb6fab5ba1547647526b666553467ecb5cb28c8d9ddf451c8f4c21?amount=1234" | ||
) | ||
).toEqual({ | ||
token: "icp", | ||
identifier: | ||
"646f4d2d6fcb6fab5ba1547647526b666553467ecb5cb28c8d9ddf451c8f4c21", | ||
amount: 1234, | ||
}); | ||
|
||
expect( | ||
decodePayment( | ||
"icp:646f4d2d6fcb6fab5ba1547647526b666553467ecb5cb28c8d9ddf451c8f4c21" | ||
) | ||
).toEqual({ | ||
token: "icp", | ||
identifier: | ||
"646f4d2d6fcb6fab5ba1547647526b666553467ecb5cb28c8d9ddf451c8f4c21", | ||
}); | ||
|
||
expect( | ||
decodePayment( | ||
"icp:646f4d2d6fcb6fab5ba1547647526b666553467ecb5cb28c8d9ddf451c8f4c21?amount=1234.456" | ||
) | ||
).toEqual({ | ||
token: "icp", | ||
identifier: | ||
"646f4d2d6fcb6fab5ba1547647526b666553467ecb5cb28c8d9ddf451c8f4c21", | ||
amount: 1234.456, | ||
}); | ||
}); | ||
|
||
it("extract payment amount information with value keyword", () => { | ||
expect( | ||
decodePayment( | ||
"ethereum:0xdAC17F958D2ee523a2206206994597C13D831ec7?value=987.321" | ||
) | ||
).toEqual({ | ||
token: "ethereum", | ||
identifier: "0xdAC17F958D2ee523a2206206994597C13D831ec7", | ||
amount: 987.321, | ||
}); | ||
}); | ||
|
||
it("should ignore content between address and parameters", () => { | ||
expect( | ||
decodePayment( | ||
"ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x8e23ee67d1332ad560396262c48ffbb01f93d052&uint256=1" | ||
) | ||
).toEqual({ | ||
token: "ethereum", | ||
identifier: "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7", | ||
}); | ||
|
||
expect( | ||
decodePayment( | ||
"ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x8e23ee67d1332ad560396262c48ffbb01f93d052&uint256=1?value=444.555" | ||
) | ||
).toEqual({ | ||
token: "ethereum", | ||
identifier: "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7", | ||
amount: 444.555, | ||
}); | ||
}); | ||
|
||
it("cannot extract payment information if token or address not provided", () => { | ||
expect( | ||
decodePayment("BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?amount=0.00001") | ||
).toBeUndefined(); | ||
|
||
expect(decodePayment("bitcoin:?amount=0.00001")).toBeUndefined(); | ||
|
||
expect( | ||
decodePayment("BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U") | ||
).toBeUndefined(); | ||
|
||
expect( | ||
decodePayment("BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U") | ||
).toBeUndefined(); | ||
|
||
expect(decodePayment("bitcoin:")).toBeUndefined(); | ||
|
||
expect(decodePayment("bitcoin")).toBeUndefined(); | ||
}); | ||
|
||
it("cannot extract payment information if amount is not a number", () => { | ||
expect( | ||
decodePayment("BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?amount=test") | ||
).toBeUndefined(); | ||
}); | ||
|
||
it("should extract amount even if multiple parameters", () => { | ||
expect( | ||
decodePayment( | ||
"ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x8e23ee67d1332ad560396262c48ffbb01f93d052&uint256=1&value=444.555" | ||
) | ||
).toEqual({ | ||
token: "ethereum", | ||
identifier: "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7", | ||
amount: 444.555, | ||
}); | ||
}); | ||
|
||
it("extract payment information for ICRC-1 as well", () => { | ||
expect( | ||
decodePayment( | ||
"chat:k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae?amount=0.00001" | ||
) | ||
).toEqual({ | ||
token: "chat", | ||
identifier: | ||
"k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae", | ||
amount: 0.00001, | ||
}); | ||
|
||
expect( | ||
decodePayment( | ||
"chat:k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-dfxgiyy.102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20?amount=120.123" | ||
) | ||
).toEqual({ | ||
token: "chat", | ||
identifier: | ||
"k2t6j-2nvnp-4zjm3-25dtz-6xhaa-c7boj-5gayf-oj3xs-i43lp-teztq-6ae-dfxgiyy.102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", | ||
amount: 120.123, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { isNullish, nonNullish } from "@dfinity/utils"; | ||
|
||
/** | ||
* A naive implementation of a payment parser. Given a code, the function attempts to extract a token name, account identifier (textual representation), and an optional amount. | ||
* | ||
* If the code doesn't match the expected pattern, `undefined` is returned for simplicity. | ||
* Similarly, if an optional amount is provided but it's not a valid number, the parser will not throw an exception and returns `undefined`. | ||
* | ||
* Please note that this function doesn't perform any validity checks on the extracted information. | ||
* It does not verify if the token is known or if the identifier is a valid address. | ||
* | ||
* urn = token ":" address [ "?" params] | ||
* token = [ ckbtc / icp / chat / bitcoin / ethereum ... ] | ||
* address = STRING | ||
* params = param [ "&" params ] | ||
* param = [ amountparam ] | ||
* amountparam = "amount=" *digit [ "." *digit ] | ||
* | ||
* @param code string | ||
* @returns { token: string; identifier: string; amount?: number } | undefined | ||
*/ | ||
export const decodePayment = ( | ||
code: string | ||
): { token: string; identifier: string; amount?: number } | undefined => { | ||
const regex = | ||
/^([a-zA-Z]+):([A-Za-z0-9:\-.]+).*?(?:[?&](?:amount|value)=(\d+(?:\.\d+)?))?$/; | ||
|
||
const match = code.match(regex); | ||
if (isNullish(match)) { | ||
return undefined; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const [_, token, identifier, amount] = match; | ||
|
||
return { | ||
token, | ||
identifier, | ||
...(nonNullish(amount) && | ||
!isNaN(parseFloat(amount)) && { amount: parseFloat(amount) }), | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters