Skip to content

Commit 9ffe0eb

Browse files
authored
Merge pull request #409 from arbuthnot-eth/feature/add-sui-support
Implement support for Sui (SUI) blockchain (784) with encoding and decoding functions
2 parents 6902461 + ece0ea2 commit 9ffe0eb

File tree

6 files changed

+101
-0
lines changed

6 files changed

+101
-0
lines changed

docs/supported-cryptocurrencies.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ The following coins are supported:
138138
| 589 | tfuel | Theta Fuel | checksummed-hex |
139139
| 592 | grin | Grin | base58check |
140140
| 714 | bnb | BNB | bech32 |
141+
| 784 | sui | Sui | hex |
141142
| 818 | vet | VeChain | checksummed-hex |
142143
| 825 | hive | Hive | base58 + ripemd160-checksum |
143144
| 888 | neo | NEO | base58check |

src/coders.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export { decodeSteemAddress, encodeSteemAddress } from "./coin/steem.js";
120120
export { decodeStratAddress, encodeStratAddress } from "./coin/strat.js";
121121
export { decodeStrkAddress, encodeStrkAddress } from "./coin/strk.js";
122122
export { decodeStxAddress, encodeStxAddress } from "./coin/stx.js";
123+
export { decodeSuiAddress, encodeSuiAddress } from "./coin/sui.js";
123124
export { decodeSysAddress, encodeSysAddress } from "./coin/sys.js";
124125
export { decodeTfuelAddress, encodeTfuelAddress } from "./coin/tfuel.js";
125126
export {

src/coin/sui.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { hexToBytes } from "@noble/hashes/utils";
2+
import { describe, expect, test } from "bun:test";
3+
import { decodeSuiAddress, encodeSuiAddress } from "./sui.js";
4+
5+
describe.each([
6+
// Test vectors from Sui documentation and examples
7+
{
8+
text: "0x21dcef5bbc5ec6d1789e8b92d3cb2c4d6855da09bd8197f8b256ca15714a7c47",
9+
hex: "21dcef5bbc5ec6d1789e8b92d3cb2c4d6855da09bd8197f8b256ca15714a7c47",
10+
},
11+
{
12+
text: "0x0000000000000000000000000000000000000000000000000000000000000001",
13+
hex: "0000000000000000000000000000000000000000000000000000000000000001",
14+
},
15+
// Test case without 0x prefix
16+
{
17+
text: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
18+
hex: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
19+
},
20+
])("sui address", ({ text, hex }) => {
21+
test(`encode: ${text}`, () => {
22+
expect(encodeSuiAddress(hexToBytes(hex))).toEqual(text.toLowerCase());
23+
});
24+
test(`decode: ${text}`, () => {
25+
expect(decodeSuiAddress(text)).toEqual(hexToBytes(hex));
26+
});
27+
});
28+
29+
test("SUI decoding - incorrect length", () => {
30+
expect(() =>
31+
decodeSuiAddress("0x21dcef5bbc5ec6d1789e8b92d3cb2c4d6855da09bd8197f8b256ca15714a7c")
32+
).toThrow("Unrecognised address format");
33+
});
34+
35+
test("SUI decoding - invalid hex", () => {
36+
expect(() =>
37+
decodeSuiAddress("0x21dcef5bbc5ec6d1789e8b92d3cb2c4d6855da09bd8197f8b256ca15714a7c4g")
38+
).toThrow("Unrecognised address format");
39+
});
40+
41+
test("SUI encoding - incorrect length", () => {
42+
expect(() =>
43+
encodeSuiAddress(
44+
hexToBytes("21dcef5bbc5ec6d1789e8b92d3cb2c4d6855da09bd8197f8b256ca15714a7c")
45+
)
46+
).toThrow("Unrecognised address format");
47+
});
48+
49+
test("SUI decoding - without 0x prefix", () => {
50+
const address = "21dcef5bbc5ec6d1789e8b92d3cb2c4d6855da09bd8197f8b256ca15714a7c47";
51+
expect(decodeSuiAddress(address)).toEqual(hexToBytes(address));
52+
});
53+
54+
test("SUI encoding/decoding - case insensitive input", () => {
55+
const upperCaseAddress = "0x21DCEF5BBC5EC6D1789E8B92D3CB2C4D6855DA09BD8197F8B256CA15714A7C47";
56+
const expectedBytes = hexToBytes("21dcef5bbc5ec6d1789e8b92d3cb2c4d6855da09bd8197f8b256ca15714a7c47");
57+
58+
expect(decodeSuiAddress(upperCaseAddress)).toEqual(expectedBytes);
59+
expect(encodeSuiAddress(expectedBytes)).toEqual(upperCaseAddress.toLowerCase());
60+
});

src/coin/sui.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { CheckedCoin } from "../types.js";
2+
import {
3+
bytesToHex,
4+
hexToBytes,
5+
} from "../utils/bytes.js";
6+
import { stripHexPrefix } from "../utils/hex.js";
7+
8+
const name = "sui";
9+
const coinType = 784;
10+
11+
export const encodeSuiAddress = (source: Uint8Array) => {
12+
if (source.length !== 32) throw new Error("Unrecognised address format");
13+
14+
const encoded = bytesToHex(source);
15+
return encoded.toLowerCase();
16+
};
17+
18+
export const decodeSuiAddress = (source: string) => {
19+
const stripped = stripHexPrefix(source);
20+
21+
// Check if the address has the correct format (64 hex chars)
22+
if (!/^0x[a-fA-F0-9]{64}$/.test(source) && !/^[a-fA-F0-9]{64}$/.test(stripped)) {
23+
throw new Error("Unrecognised address format");
24+
}
25+
26+
const decoded = hexToBytes(`0x${stripped}` as const);
27+
if (decoded.length !== 32) throw new Error("Unrecognised address format");
28+
29+
return decoded;
30+
};
31+
32+
export const sui = {
33+
name,
34+
coinType,
35+
encode: encodeSuiAddress,
36+
decode: decodeSuiAddress,
37+
} as const satisfies CheckedCoin;

src/coins.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export { steem } from "./coin/steem.js";
9393
export { strat } from "./coin/strat.js";
9494
export { strk } from "./coin/strk.js";
9595
export { stx } from "./coin/stx.js";
96+
export { sui } from "./coin/sui.js";
9697
export { sys } from "./coin/sys.js";
9798
export { tfuel } from "./coin/tfuel.js";
9899
export { thetaLegacy } from "./coin/thetaLegacy.js";

src/consts/coinTypeToNameMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export const nonEvmCoinTypeToNameMap = Object.freeze({
141141
"592": ["grin", "Grin"],
142142
"700": ["gnoLegacy", "[LEGACY] Gnosis"],
143143
"714": ["bnb", "BNB"],
144+
"784": ["sui", "Sui"],
144145
"818": ["vet", "VeChain"],
145146
"820": ["cloLegacy", "[LEGACY] Callisto"],
146147
"825": ["hive", "Hive"],

0 commit comments

Comments
 (0)