Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d78ca0b
OKX Wallet connects Solana
hippocampusSK Oct 8, 2026
b9eeb6b
OKX Wallet connects Starknet
hippocampusSK Oct 8, 2026
1ce4df7
OKX Wallet connects Cardano
hippocampusSK Oct 8, 2026
9c59aa2
OKX Wallet keeps the chains it can connect when another one is not av…
hippocampusSK Oct 8, 2026
58ce4ba
Fix the OKX Cardano type declarations build
hippocampusSK Oct 12, 2026
668b4cc
OKX Wallet signs Cardano with the shared SDK helpers
hippocampusSK Oct 12, 2026
3ea21d0
Merge remote-tracking branch 'origin/develop' into feature/okx-solana…
hippocampusSK Oct 12, 2026
16ac233
Use a real Cardano address in the OKX tests
hippocampusSK Oct 13, 2026
593fb2c
OKX Mobile offers the same chains as the OKX extension
hippocampusSK Oct 13, 2026
bf1aa5f
Read the OKX Starknet account after the wallet is enabled
hippocampusSK Oct 13, 2026
a653ba1
Release the OKX Mobile connector change as a major version
hippocampusSK Oct 13, 2026
38d14b7
Simplify the OKX Starknet account type and note undeployed accounts
hippocampusSK Oct 13, 2026
1d5d97e
Merge remote-tracking branch 'origin/develop' into feature/okx-solana…
hippocampusSK Oct 13, 2026
08a7be5
Restore the lockfile from develop
hippocampusSK Oct 13, 2026
f3c67b0
OKX Wallet only skips chains it does not expose and reports other con…
hippocampusSK Oct 14, 2026
04f2f49
Connect OKX Cardano alongside the other chains
hippocampusSK Oct 14, 2026
574cca1
Note that OKX Mobile no longer follows EVM account switches
hippocampusSK Oct 14, 2026
f347a5e
OKX Wallet connects Sui
hippocampusSK Oct 14, 2026
ec2305c
OKX Wallet asks for one chain at a time when connecting
hippocampusSK Oct 14, 2026
0dc4931
Merge branch 'feature/okx-solana-starknet-cardano' into feature/okx-sui
hippocampusSK Oct 14, 2026
95f4406
OKX Wallet connects Arc, HyperEVM and Robinhood
hippocampusSK Oct 14, 2026
5b686a2
Merge branch 'feature/okx-solana-starknet-cardano' into feature/okx-sui
hippocampusSK Oct 14, 2026
de0d43b
Move OKX Starknet support to its own pull request
hippocampusSK Oct 14, 2026
20712c8
Merge branch 'feature/okx-solana-starknet-cardano' into feature/okx-sui
hippocampusSK Oct 14, 2026
1607a71
Merge branch 'develop' into feature/okx-sui
hippocampusSK Oct 16, 2026
6fd51c9
Merge branch 'develop' into feature/okx-sui
hippocampusSK Oct 16, 2026
8fa809b
OKX Wallet checks Sui mainnet and signing support when connecting
hippocampusSK Oct 16, 2026
1d12280
Merge branch 'develop' into feature/okx-sui
hippocampusSK Oct 26, 2026
adac2fd
Move OKX Sui connection to its own module and test it with the real w…
hippocampusSK Oct 26, 2026
30bfed8
OKX Wallet signs Sui with the current signing method only
hippocampusSK Oct 26, 2026
f92ca59
Give the OKX Sui connector an explicit return type so type declaratio…
hippocampusSK Oct 26, 2026
e792682
Merge branch 'develop' into feature/okx-sui
hippocampusSK Nov 4, 2026
82d1a0f
OKX Wallet skips Sui when it cannot sign and rejects signatures over …
hippocampusSK Nov 4, 2026
0ca994e
OKX Wallet reports a changed Sui transaction as a signing error
hippocampusSK Nov 4, 2026
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
6 changes: 6 additions & 0 deletions .changeset/okx-sui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@swapkit/wallet-extensions": minor
"@swapkit/wallets": minor
---

OKX Wallet now connects Sui. OKX does not inject a Sui provider on `window`, so it is found through the Wallet Standard as "OKX Wallet".
75 changes: 75 additions & 0 deletions packages/wallet-extensions/src/okx/__tests__/okx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { afterEach, describe, expect, test } from "bun:test";
import { bech32, hex } from "@scure/base";
import { AssetValue, Chain } from "@swapkit/helpers";
import { Address, beginCell } from "@ton/core";
import { getWallets } from "@wallet-standard/app";

import { getWalletMethods } from "../helpers";
import { okxWallet } from "../index";

const SUI_ADDRESS = `0x${"7".repeat(64)}`;

const RAW_TON_ADDRESS = `0:${"ab".repeat(32)}`;

// CIP-19 mainnet base address test vector
Expand Down Expand Up @@ -111,6 +114,78 @@ describe("connectOkx", () => {
});
});

describe("OKX Sui", () => {
const unregisterWallets = [];

function registerSuiWallet({ chains = ["sui:mainnet"], signFeature = true, signedBytes = undefined } = {}) {
const requests = [];
const features = {
"standard:connect": {
connect: () => {
requests.push("connect");
return Promise.resolve({ accounts: [{ address: SUI_ADDRESS, chains }] });
},
},
};
if (signFeature) {
features["sui:signTransaction"] = {
signTransaction: async (input) => {
const transaction = await input.transaction.toJSON();
requests.push({ ...input, transaction });
return { bytes: signedBytes ?? transaction, signature: "c2lnbmF0dXJl" };
},
};
}
unregisterWallets.push(
getWallets().register({ accounts: [], chains: ["sui:mainnet"], features, name: "OKX Wallet" }),
);
return requests;
}

afterEach(() => {
for (const unregister of unregisterWallets.splice(0)) unregister();
});

test("asks the wallet standard account to sign the exact transaction bytes", async () => {
const requests = registerSuiWallet();

const wallet = await getWalletMethods(Chain.Sui);
const signed = await wallet.signTransaction(Uint8Array.from([1, 2, 3]));

expect(wallet.address).toBe(SUI_ADDRESS);
expect(requests).toEqual([
"connect",
{ account: { address: SUI_ADDRESS, chains: ["sui:mainnet"] }, chain: "sui:mainnet", transaction: "AQID" },
]);
expect(signed).toEqual({ bytes: "AQID", signature: "c2lnbmF0dXJl" });
});

test("rejects a signature over bytes the wallet changed", async () => {
registerSuiWallet({ signedBytes: "c2lnbmVk" });

const wallet = await getWalletMethods(Chain.Sui);

await expect(wallet.signTransaction(Uint8Array.from([1, 2, 3]))).rejects.toThrow("toolbox_sui_signing_error");
});

test("rejects a wallet that only returns non-mainnet accounts", async () => {
registerSuiWallet({ chains: ["sui:testnet"] });

await expect(getWalletMethods(Chain.Sui)).rejects.toThrow("wallet_okx_chain_not_supported");
});

test("skips Sui without a signing feature and keeps the other chains", async () => {
const requests = registerSuiWallet({ signFeature: false });
globalThis.window = { okxwallet: { cardano: mockCardano() } };
const added = [];
const connectOkx = okxWallet.connectOkx.connectWallet({ addChain: (wallet) => added.push(wallet) });

expect(await connectOkx([Chain.Cardano, Chain.Sui])).toBe(true);
expect(added.map(({ chain }) => chain)).toEqual([Chain.Cardano]);
expect(requests).toEqual([]);
});
});

describe("OKX TON", () => {
function mockTonBridge(requests: unknown[]) {
globalThis.window = {
Expand Down
2 changes: 2 additions & 0 deletions packages/wallet-extensions/src/okx/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { TronSignedTransaction, TronSigner, TronTransaction } from "@swapki
import { Transaction } from "@swapkit/utxo-signer";
import type { Eip1193Provider } from "ethers";
import { getUtxoScriptTypeParams } from "../helpers/utxoScriptType";
import { getOkxSuiWallet } from "./sui";
import { getOkxTonWallet } from "./ton";

type WalletMethodsWithAddress = Record<string, unknown> & { address: string };
Expand Down Expand Up @@ -195,6 +196,7 @@ export async function getWalletMethods(chain: Chain): Promise<WalletMethodsWithA

return { ...getCardanoToolbox({ signer }), address };
})
.with(Chain.Sui, () => getOkxSuiWallet())
.with(Chain.Ton, () => getOkxTonWallet())
.with(Chain.Cosmos, async () => {
if (!(window.okxwallet && "keplr" in window.okxwallet)) {
Expand Down
2 changes: 2 additions & 0 deletions packages/wallet-extensions/src/okx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const okxWallet: ExtensionWallet<"connectOkx"> = createWallet({
[Chain.Robinhood]: { direct: true, typedData: true },
[Chain.Solana]: { direct: true },
[Chain.Starknet]: { direct: true },
[Chain.Sui]: { direct: true },
[Chain.Ton]: { direct: true },
[Chain.Tron]: { direct: true },
[Chain.XLayer]: { direct: true, typedData: true },
Expand Down Expand Up @@ -78,6 +79,7 @@ export const okxWallet: ExtensionWallet<"connectOkx"> = createWallet({
Chain.Robinhood,
Chain.Solana,
Chain.Starknet,
Chain.Sui,
Chain.XLayer,
Chain.Ton,
Chain.Tron,
Expand Down
53 changes: 53 additions & 0 deletions packages/wallet-extensions/src/okx/sui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { base64 } from "@scure/base";
import { Chain, SwapKitError } from "@swapkit/helpers";
import type { SuiWallet } from "@swapkit/toolboxes/sui";

const SUI_MAINNET = "sui:mainnet";

type SuiAccount = { address: string; chains: readonly string[] };
type SuiSignInput = { account: SuiAccount; chain: string; transaction: unknown };
type OkxSuiFeatures = {
"standard:connect"?: { connect: () => Promise<{ accounts: readonly SuiAccount[] }> };
"sui:signTransaction"?: { signTransaction: (input: SuiSignInput) => Promise<{ bytes: string; signature: string }> };
};

export async function getOkxSuiWallet(): Promise<SuiWallet & { address: string }> {
const { getWallets } = await import("@wallet-standard/app");
const wallet = getWallets()
.get()
.find(({ chains, name }) => name === "OKX Wallet" && chains.includes(SUI_MAINNET));
const features = wallet?.features as OkxSuiFeatures | undefined;
const connect = features?.["standard:connect"];
const signFeature = features?.["sui:signTransaction"];

if (!(connect && signFeature)) {
throw new SwapKitError("wallet_okx_not_found", { chain: Chain.Sui });
}

const { accounts } = await connect.connect();
const account = accounts.find(({ chains }) => chains.includes(SUI_MAINNET));

if (!account) {
throw new SwapKitError("wallet_okx_chain_not_supported", { chain: Chain.Sui, message: "Mainnet only" });
}

const signTransaction = async (txBytes: Uint8Array) => {
const serialized = base64.encode(txBytes);
const transaction = { serialize: () => serialized, toJSON: () => Promise.resolve(serialized) };
const signed = await signFeature.signTransaction({ account, chain: SUI_MAINNET, transaction });

if (signed.bytes !== serialized) {
throw new SwapKitError("toolbox_sui_signing_error", {
chain: Chain.Sui,
message: "Wallet modified the transaction",
});
}

return signed;
};

const { getSuiToolbox } = await import("@swapkit/toolboxes/sui");
const signer = { getAddress: () => account.address, signTransaction, toSuiAddress: () => account.address };

return { ...getSuiToolbox({ signer }), address: account.address };
}
Loading