Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
94655a4
feat: add SafeCard validation with JIT signing checks
gomesalexandre Nov 26, 2025
cc6ec1e
chore: version packages to 1.62.13-gridplus-validation.7
gomesalexandre Nov 26, 2025
1a2de02
refactor: simplify validation API - throw instead of return isValid
gomesalexandre Nov 26, 2025
9963fd5
refactor: change isExternal to isInternal with reversed logic
gomesalexandre Nov 26, 2025
fed33f6
refactor: use type field and remove all console.logs
gomesalexandre Nov 26, 2025
df67c40
chore: version to 1.62.13-gridplus-validation.10
gomesalexandre Nov 26, 2025
a30995b
fix: use cached getActiveWallet instead of fetching again
gomesalexandre Nov 26, 2025
92bcc7f
refactor: rename walletUid to activeWalletId for consistency
gomesalexandre Nov 26, 2025
02f554f
fix: remove resetActiveWallets() call that cleared cache during pairing
gomesalexandre Nov 26, 2025
abf79f4
chore: restore resetActiveWallets() call in connectDevice()
gomesalexandre Nov 26, 2025
6b5a48f
feat: add specific error message for internal wallet access
gomesalexandre Nov 26, 2025
080c811
refactor: add isEmptyWallet helper for cleaner buffer comparisons
gomesalexandre Nov 26, 2025
4d193f4
refactor: optimize buffer comparison to avoid allocation
gomesalexandre Nov 26, 2025
980bb88
feat: add type-aware SafeCard validation error messages
gomesalexandre Nov 26, 2025
b9a90c7
feat: pass expectedType through all validation call paths
gomesalexandre Nov 26, 2025
f84db93
chore: revert package.json version bumps to master baseline
gomesalexandre Nov 26, 2025
f79fac9
chore: revert sandbox and integration package.json to master
gomesalexandre Nov 26, 2025
8b6bbd0
chore: restore comment explaining resetActiveWallets purpose
gomesalexandre Nov 26, 2025
e638101
chore: remove unnecessary braces and comment
gomesalexandre Nov 26, 2025
9779859
chore: remove redundant type annotation
gomesalexandre Nov 26, 2025
8cfee51
refactor: extract SafeCardType for reusability
gomesalexandre Nov 26, 2025
503a451
chore: remove unused walletName from validation return
gomesalexandre Nov 26, 2025
7f36a79
refactor: use viem zeroHash for cleaner empty wallet check
gomesalexandre Nov 26, 2025
63ec5fe
chore: lint fix - quote style
gomesalexandre Nov 26, 2025
45b2fee
refactor: rename isEmptyWallet to isZeroSafecardUid and fix stale com…
gomesalexandre Nov 26, 2025
d4763b4
refactor: rename to isSafecardDisconnected for clarity
gomesalexandre Nov 26, 2025
68f2227
refactor: reverse logic to isSafecardConnected for clarity
gomesalexandre Nov 26, 2025
adc51f4
refactor: throw error if expected ID not set before signing
gomesalexandre Nov 26, 2025
b00dfb2
[skip ci] trigger CI skip
gomesalexandre Nov 26, 2025
e1f9119
[skip ci] chore: remove unnecessary braces from single-line if
gomesalexandre Nov 26, 2025
7d31e00
[skip ci] chore: simplify error message
gomesalexandre Nov 26, 2025
acfa893
[skip ci] chore: add blank lines after guard clauses
gomesalexandre Nov 26, 2025
fc4723c
[skip ci] refactor: remove unused password parameter from connectDevice
gomesalexandre Nov 26, 2025
9cf7045
chore: trigger CI
gomesalexandre Nov 26, 2025
2938229
chore: version to 1.62.13-gridplus-validation.17
gomesalexandre Nov 26, 2025
be61514
[skip ci] refactor: export SafeCardType from gridplus and import in a…
gomesalexandre Nov 27, 2025
d5ed205
[skip ci] fix: update sandbox to destructure pairDevice result
gomesalexandre Nov 27, 2025
b99f6b8
Merge branch 'master' into feat_gridplus_validation_v2
gomesalexandre Nov 28, 2025
50218b2
chore: version packages to 1.62.16-bro-seriously-fml.0
gomesalexandre Nov 28, 2025
951a647
chore: update internal dependencies after prepublish build
gomesalexandre Nov 28, 2025
f26738b
chore: version packages to 1.62.16-bro-seriously-fml.1
gomesalexandre Nov 28, 2025
1e845ea
chore: update internal dependencies after prepublish build
gomesalexandre Nov 28, 2025
af2fe3b
feat: prepare for yeet
gomesalexandre Nov 28, 2025
3d5e95b
feat: yarn.lock too
gomesalexandre Nov 28, 2025
b32d09c
chore(release): publish 1.62.16
gomesalexandre Nov 28, 2025
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
3 changes: 2 additions & 1 deletion examples/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ $gridplus.on("click", async (e) => {
window["pairingCodeEntered"] = async function () {
const input = document.getElementById("#pairingCodeInput") as HTMLInputElement;
document.getElementById("#pairingCodeModal").className = "modal";
wallet = await gridplusAdapter.pairDevice(input.value);
const { wallet: gridplusWallet } = await gridplusAdapter.pairDevice(input.value);
wallet = gridplusWallet;
window["wallet"] = wallet;
$("#keyring select").val(await wallet.getDeviceID());
};
Expand Down
38 changes: 19 additions & 19 deletions examples/sandbox/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/hdwallet-sandbox",
"version": "1.62.15",
"version": "1.62.16",
"license": "MIT",
"private": true,
"browserslist": "> 0.5%, last 2 versions, not dead",
Expand All @@ -12,24 +12,24 @@
"dependencies": {
"@esm2cjs/p-queue": "^7.3.0",
"@metamask/eth-sig-util": "^7.0.0",
"@shapeshiftoss/hdwallet-coinbase": "1.62.15",
"@shapeshiftoss/hdwallet-core": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey-tcp": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.62.15",
"@shapeshiftoss/hdwallet-keplr": "1.62.15",
"@shapeshiftoss/hdwallet-ledger": "1.62.15",
"@shapeshiftoss/hdwallet-ledger-webhid": "1.62.15",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.62.15",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.62.15",
"@shapeshiftoss/hdwallet-native": "1.62.15",
"@shapeshiftoss/hdwallet-phantom": "1.62.15",
"@shapeshiftoss/hdwallet-portis": "1.62.15",
"@shapeshiftoss/hdwallet-trezor": "1.62.15",
"@shapeshiftoss/hdwallet-trezor-connect": "1.62.15",
"@shapeshiftoss/hdwallet-vultisig": "1.62.15",
"@shapeshiftoss/hdwallet-walletconnect": "1.62.15",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.62.15",
"@shapeshiftoss/hdwallet-coinbase": "1.62.16",
"@shapeshiftoss/hdwallet-core": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey-tcp": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.62.16",
"@shapeshiftoss/hdwallet-keplr": "1.62.16",
"@shapeshiftoss/hdwallet-ledger": "1.62.16",
"@shapeshiftoss/hdwallet-ledger-webhid": "1.62.16",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.62.16",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.62.16",
"@shapeshiftoss/hdwallet-native": "1.62.16",
"@shapeshiftoss/hdwallet-phantom": "1.62.16",
"@shapeshiftoss/hdwallet-portis": "1.62.16",
"@shapeshiftoss/hdwallet-trezor": "1.62.16",
"@shapeshiftoss/hdwallet-trezor-connect": "1.62.16",
"@shapeshiftoss/hdwallet-vultisig": "1.62.16",
"@shapeshiftoss/hdwallet-walletconnect": "1.62.16",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.62.16",
"bip32": "^2.0.4",
"eip-712": "^1.0.0",
"jquery": "^3.7.1",
Expand Down
22 changes: 11 additions & 11 deletions integration/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/integration",
"version": "1.62.15",
"version": "1.62.16",
"main": "index.js",
"license": "MIT",
"private": true,
Expand All @@ -11,16 +11,16 @@
},
"dependencies": {
"@bitcoinerlab/secp256k1": "^1.1.1",
"@shapeshiftoss/hdwallet-core": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey-nodewebusb": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey-tcp": "1.62.15",
"@shapeshiftoss/hdwallet-ledger": "1.62.15",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.62.15",
"@shapeshiftoss/hdwallet-native": "1.62.15",
"@shapeshiftoss/hdwallet-portis": "1.62.15",
"@shapeshiftoss/hdwallet-trezor": "1.62.15",
"@shapeshiftoss/hdwallet-vultisig": "1.62.15",
"@shapeshiftoss/hdwallet-core": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey-nodewebusb": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey-tcp": "1.62.16",
"@shapeshiftoss/hdwallet-ledger": "1.62.16",
"@shapeshiftoss/hdwallet-metamask-multichain": "1.62.16",
"@shapeshiftoss/hdwallet-native": "1.62.16",
"@shapeshiftoss/hdwallet-portis": "1.62.16",
"@shapeshiftoss/hdwallet-trezor": "1.62.16",
"@shapeshiftoss/hdwallet-vultisig": "1.62.16",
"fast-json-stable-stringify": "^2.1.0",
"msw": "^0.27.1",
"whatwg-fetch": "^3.6.2"
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"lerna": "5.2.0",
"version": "1.62.15",
"version": "1.62.16",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
Expand Down
4 changes: 2 additions & 2 deletions packages/hdwallet-coinbase/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/hdwallet-coinbase",
"version": "1.62.15",
"version": "1.62.16",
"license": "MIT",
"publishConfig": {
"access": "public"
Expand All @@ -15,7 +15,7 @@
},
"dependencies": {
"@coinbase/wallet-sdk": "^3.6.6",
"@shapeshiftoss/hdwallet-core": "1.62.15",
"@shapeshiftoss/hdwallet-core": "1.62.16",
"eth-rpc-errors": "^4.0.3",
"lodash": "^4.17.21"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/hdwallet-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/hdwallet-core",
"version": "1.62.15",
"version": "1.62.16",
"license": "MIT",
"publishConfig": {
"access": "public"
Expand Down
4 changes: 2 additions & 2 deletions packages/hdwallet-gridplus/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/hdwallet-gridplus",
"version": "1.62.15",
"version": "1.62.16",
"license": "MIT",
"publishConfig": {
"access": "public"
Expand All @@ -20,7 +20,7 @@
"@ethereumjs/rlp": "5.0.2",
"@ethereumjs/tx": "5.4.0",
"@metamask/eth-sig-util": "^7.0.0",
"@shapeshiftoss/hdwallet-core": "1.62.15",
"@shapeshiftoss/hdwallet-core": "1.62.16",
"bech32": "^1.1.4",
"bs58": "^5.0.0",
"crypto-js": "^4.2.0",
Expand Down
36 changes: 30 additions & 6 deletions packages/hdwallet-gridplus/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as core from "@shapeshiftoss/hdwallet-core";
import { createHash } from "crypto";
import { Client } from "gridplus-sdk";

import { GridPlusHDWallet } from "./gridplus";
import { GridPlusHDWallet, SafeCardType } from "./gridplus";

const name = "ShapeShift";
const baseUrl = "https://signing.gridpl.us";
Expand All @@ -20,9 +20,13 @@ export class GridPlusAdapter {
return new GridPlusAdapter(keyring);
}

public async connectDevice(deviceId: string, password = ""): Promise<GridPlusHDWallet | undefined> {
public async connectDevice(
deviceId: string,
expectedActiveWalletId?: string,
expectedType?: SafeCardType
): Promise<GridPlusHDWallet | undefined> {
const privKey = createHash("sha256")
.update(deviceId + password + name)
.update(deviceId + name)
.digest();

if (!this.client) {
Expand All @@ -34,10 +38,20 @@ export class GridPlusAdapter {
}

const isPaired = await this.client.connect(deviceId);
if (isPaired) return new GridPlusHDWallet(this.client);
if (!isPaired) return undefined;

const wallet = new GridPlusHDWallet(this.client);

if (expectedActiveWalletId) await wallet.validateActiveWallet(expectedActiveWalletId, expectedType);

return wallet;
}

public async pairDevice(pairingCode: string): Promise<GridPlusHDWallet> {
public async pairDevice(pairingCode: string): Promise<{
wallet: GridPlusHDWallet;
activeWalletId: string;
type: SafeCardType;
}> {
if (!this.client) throw new Error("No client connected. Call connectDevice first.");

const success = await this.client.pair(pairingCode);
Expand All @@ -46,6 +60,16 @@ export class GridPlusAdapter {
const wallet = new GridPlusHDWallet(this.client);
this.keyring.add(wallet, this.client.getDeviceId());

return wallet;
const activeWallet = this.client.getActiveWallet();
if (!activeWallet) throw new Error("No active wallet found on device");

const activeWalletId = activeWallet.uid.toString("hex");
const type = activeWallet.external ? "external" : "internal";

return {
wallet,
activeWalletId,
type,
};
}
}
77 changes: 74 additions & 3 deletions packages/hdwallet-gridplus/src/gridplus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as core from "@shapeshiftoss/hdwallet-core";
import { Client, Constants } from "gridplus-sdk";
import isObject from "lodash/isObject";
import { zeroHash } from "viem";

import * as btc from "./bitcoin";
import * as cosmos from "./cosmos";
Expand All @@ -9,7 +10,9 @@ import * as mayachain from "./mayachain";
import * as solana from "./solana";
import * as thorchain from "./thorchain";

const ZERO_BUFFER = Buffer.alloc(32);
export type SafeCardType = "external" | "internal";

const isSafecardConnected = (uid?: Buffer): boolean => !!uid && `0x${uid.toString("hex")}` !== zeroHash;

export function isGridPlus(wallet: core.HDWallet): wallet is GridPlusHDWallet {
return isObject(wallet) && (wallet as any)._isGridPlus;
Expand Down Expand Up @@ -299,12 +302,19 @@ export class GridPlusHDWallet
readonly _isGridPlus = true;

client: Client | undefined;
private expectedActiveWalletId?: string;
private expectedType?: SafeCardType;

constructor(client: Client) {
super();
this.client = client;
}

public setExpectedActiveWalletId(activeWalletId: string, type?: SafeCardType): void {
this.expectedActiveWalletId = activeWalletId;
this.expectedType = type;
}

async cancel(): Promise<void> {}
async clearSession(): Promise<void> {}
async initialize(): Promise<void> {}
Expand Down Expand Up @@ -367,8 +377,45 @@ export class GridPlusHDWallet

const { external, internal } = await this.client.fetchActiveWallet();

if (!external.uid.equals(ZERO_BUFFER)) return external.uid.toString("hex");
if (!internal.uid.equals(ZERO_BUFFER)) return internal.uid.toString("hex");
if (isSafecardConnected(external.uid)) return external.uid.toString("hex");
if (isSafecardConnected(internal.uid)) return internal.uid.toString("hex");
}

/**
* Validate that the currently active wallet matches expectations
* Throws if expectedActiveWalletId is provided and doesn't match
*/
public async validateActiveWallet(
expectedActiveWalletId?: string,
expectedType?: SafeCardType
): Promise<{
activeWalletId: string;
type: SafeCardType;
}> {
if (!this.client) throw new Error("Device not connected");

const activeWallets = await this.client.fetchActiveWallet();

// Determine active wallet type (external SafeCard takes priority)
const type: SafeCardType = (() => {
if (isSafecardConnected(activeWallets.external?.uid)) return "external";
if (isSafecardConnected(activeWallets.internal?.uid)) return "internal";

throw new Error("No active wallet found on device");
})();

const activeWallet = activeWallets[type];
const activeWalletId = activeWallet.uid.toString("hex");

// Validate against expected activeWalletId if provided
if (expectedActiveWalletId && activeWalletId !== expectedActiveWalletId) {
if (expectedType === "internal") {
throw new Error("Remove inserted SafeCard to access internal GridPlus wallet");
}
throw new Error("Active SafeCard doesn't match expected SafeCard");
}

return { activeWalletId, type };
}

async getPublicKeys(msg: Array<core.GetPublicKey>): Promise<Array<core.PublicKey | null>> {
Expand Down Expand Up @@ -426,6 +473,9 @@ export class GridPlusHDWallet

async btcSignTx(msg: core.BTCSignTx): Promise<core.BTCSignedTx | null> {
if (!this.client) throw new Error("Device not connected");
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return btc.btcSignTx(this.client, msg);
}

Expand All @@ -444,16 +494,25 @@ export class GridPlusHDWallet

async ethSignTx(msg: core.ETHSignTx): Promise<core.ETHSignedTx> {
if (!this.client) throw new Error("Device not connected");
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return eth.ethSignTx(this.client, msg);
}

async ethSignTypedData(msg: core.ETHSignTypedData): Promise<core.ETHSignedTypedData> {
if (!this.client) throw new Error("Device not connected");
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return eth.ethSignTypedData(this.client, msg);
}

async ethSignMessage(msg: core.ETHSignMessage): Promise<core.ETHSignedMessage> {
if (!this.client) throw new Error("Device not connected");
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return eth.ethSignMessage(this.client, msg);
}

Expand All @@ -480,6 +539,9 @@ export class GridPlusHDWallet

async solanaSignTx(msg: core.SolanaSignTx): Promise<core.SolanaSignedTx | null> {
this.assertSolanaFwSupport();
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return solana.solanaSignTx(this.client, msg);
}

Expand All @@ -490,6 +552,9 @@ export class GridPlusHDWallet

async cosmosSignTx(msg: core.CosmosSignTx): Promise<core.CosmosSignedTx | null> {
if (!this.client) throw new Error("Device not connected");
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return cosmos.cosmosSignTx(this.client, msg);
}

Expand All @@ -500,6 +565,9 @@ export class GridPlusHDWallet

async thorchainSignTx(msg: core.ThorchainSignTx): Promise<core.ThorchainSignedTx | null> {
if (!this.client) throw new Error("Device not connected");
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return thorchain.thorchainSignTx(this.client, msg);
}

Expand All @@ -510,6 +578,9 @@ export class GridPlusHDWallet

async mayachainSignTx(msg: core.MayachainSignTx): Promise<core.MayachainSignedTx | null> {
if (!this.client) throw new Error("Device not connected");
if (!this.expectedActiveWalletId) throw new Error("Expected SafeCard ID not set");

await this.validateActiveWallet(this.expectedActiveWalletId, this.expectedType);
return mayachain.mayachainSignTx(this.client, msg);
}
}
6 changes: 3 additions & 3 deletions packages/hdwallet-keepkey-chromeusb/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shapeshiftoss/hdwallet-keepkey-chromeusb",
"version": "1.62.15",
"version": "1.62.16",
"license": "MIT",
"publishConfig": {
"access": "public"
Expand All @@ -14,7 +14,7 @@
"prepublishOnly": "yarn clean && yarn build"
},
"dependencies": {
"@shapeshiftoss/hdwallet-core": "1.62.15",
"@shapeshiftoss/hdwallet-keepkey": "1.62.15"
"@shapeshiftoss/hdwallet-core": "1.62.16",
"@shapeshiftoss/hdwallet-keepkey": "1.62.16"
}
}
Loading