Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@mymonero/mymonero-keyimage-cache": "^3.0.0",
"@noble/secp256k1": "1.7.1",
"@onekeyfe/bitcoinforksjs-lib": "7.0.0-rc.0",
"@onekeyfe/cardano-coin-selection-asmjs": "1.1.10-alpha.8",
"@onekeyfe/cardano-coin-selection-asmjs": "1.1.11",
"@onekeyfe/coinselect": "3.1.17",
"@onekeyfe/kaspa-core-lib": "^1.0.2",
"@onekeyfe/kaspa-wasm": "^1.0.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/chains/ada/CoreChainSoftware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export default class CoreChainSoftware extends CoreChainApiBase {
encodedTx.inputs as unknown as IAdaUTXO[],
xprv,
encodedTx.staking?.isStakingTx ? false : !!encodedTx.signOnly,
false,
!!encodedTx.partialSign,
);

return {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/chains/ada/sdkAda/sdk/adaWebSdk.jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const getCardanoApi: IGetCardanoApi = async () => ({
dAppGetUtxos: Loader.dAppUtils.getUtxos,
dAppConvertCborTxToEncodeTx: Loader.dAppUtils.convertCborTxToEncodeTx,
dAppSignData: Loader.dAppUtils.signData,
parseRawTxInputs: Loader.onekeyUtils.parseRawTxInputs,
parseRawTxBodyStakeInfo: Loader.onekeyUtils.parseRawTxBodyStakeInfo,
extractStakeKeyHashFromBaseAddress:
Loader.onekeyUtils.extractStakeKeyHashFromBaseAddress,
});

export default {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/chains/ada/sdkAda/sdk/adaWebSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const getCardanoApi: IGetCardanoApi = async () => {
dAppGetUtxos: Loader.dAppUtils.getUtxos,
dAppConvertCborTxToEncodeTx: Loader.dAppUtils.convertCborTxToEncodeTx,
dAppSignData: Loader.dAppUtils.signData,
parseRawTxInputs: Loader.onekeyUtils.parseRawTxInputs,
parseRawTxBodyStakeInfo: Loader.onekeyUtils.parseRawTxBodyStakeInfo,
extractStakeKeyHashFromBaseAddress:
Loader.onekeyUtils.extractStakeKeyHashFromBaseAddress,
};
};

Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/chains/ada/sdkAda/sdk/index.ext-bg-v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ const dAppConvertCborTxToEncodeTx = async (...args: any[]) =>
const dAppSignData = async (...args: any[]) =>
appGlobals.$offscreenApiProxy.adaSdk.dAppSignData(...args);

const parseRawTxInputs = async (rawTxHex: string) =>
appGlobals.$offscreenApiProxy.adaSdk.parseRawTxInputs(rawTxHex);

const parseRawTxBodyStakeInfo = async (rawTxHex: string) =>
appGlobals.$offscreenApiProxy.adaSdk.parseRawTxBodyStakeInfo(rawTxHex);

const extractStakeKeyHashFromBaseAddress = async (addr: string) =>
appGlobals.$offscreenApiProxy.adaSdk.extractStakeKeyHashFromBaseAddress(addr);

const getCardanoApi: IGetCardanoApi = async () =>
Promise.resolve({
composeTxPlan,
Expand All @@ -46,6 +55,9 @@ const getCardanoApi: IGetCardanoApi = async () =>
dAppGetUtxos,
dAppConvertCborTxToEncodeTx,
dAppSignData,
parseRawTxInputs,
parseRawTxBodyStakeInfo,
extractStakeKeyHashFromBaseAddress,
});

const sdk: IAdaSdk = { getCardanoApi, ensureSDKReady };
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/chains/ada/sdkAda/sdk/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ export interface IAdaSdkApi {
dAppGetUtxos: typeof import('@onekeyfe/cardano-coin-selection-asmjs').dAppUtils.getUtxos;
dAppConvertCborTxToEncodeTx: typeof import('@onekeyfe/cardano-coin-selection-asmjs').dAppUtils.convertCborTxToEncodeTx;
dAppSignData: typeof import('@onekeyfe/cardano-coin-selection-asmjs').dAppUtils.signData;
parseRawTxInputs: typeof import('@onekeyfe/cardano-coin-selection-asmjs').onekeyUtils.parseRawTxInputs;
parseRawTxBodyStakeInfo: typeof import('@onekeyfe/cardano-coin-selection-asmjs').onekeyUtils.parseRawTxBodyStakeInfo;
extractStakeKeyHashFromBaseAddress: typeof import('@onekeyfe/cardano-coin-selection-asmjs').onekeyUtils.extractStakeKeyHashFromBaseAddress;
}
1 change: 1 addition & 0 deletions packages/core/src/chains/ada/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export type IEncodedTxAda = {
tx: IAdaTxInfo;
changeAddress: IAdaChangeAddress;
signOnly?: boolean;
partialSign?: boolean;
staking?: IAdaStakingInfo;
};

Expand Down
28 changes: 28 additions & 0 deletions packages/kit-bg/src/dbs/local/LocalDbBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2413,6 +2413,15 @@ export abstract class LocalDbBase extends LocalDbBaseContainer {
}

async updateFirmwareVerified(params: IDBUpdateFirmwareVerifiedParams) {
// [diagnostic] snapshot oldValue before the write
let oldValue: string | undefined;
try {
const existing = await this.getDeviceSafe(params.device.id);
oldValue = existing?.verifiedAtVersion;
} catch {
// ignore — diagnostic only
}

await this.withTransaction(EIndexedDBBucketNames.account, async (tx) => {
const { device, verifyResult } = params;
const { id, featuresInfo, features } = device;
Expand Down Expand Up @@ -2440,6 +2449,25 @@ export abstract class LocalDbBase extends LocalDbBaseContainer {
},
});
});

// [diagnostic] log degradation after the write
try {
const wasValid = typeof oldValue === 'string' && oldValue.length > 0;
const becomesEmpty = params.verifyResult !== 'official';
if (wasValid && becomesEmpty) {
defaultLogger.hardware.verify.deviceVerifiedAtVersionCleared({
deviceId: params.device.id,
oldValue: oldValue as string,
newValueRaw: JSON.stringify(params.verifyResult),
stack: new Error('verifiedAtVersion-tripwire').stack
?.split('\n')
.slice(2, 18)
.join('\n'),
});
}
} catch {
// diagnostic logging must never break the DB write
}
}

async updateDevice({
Expand Down
15 changes: 15 additions & 0 deletions packages/kit-bg/src/offscreens/OffscreenApiAdaSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,19 @@ export default class OffscreenApiAdaSdk implements IAdaSdkApi {
// @ts-ignore
return api.dAppSignData(...args);
}

async parseRawTxInputs(rawTxHex: string) {
const api = await adaWebSdk.getCardanoApi();
return api.parseRawTxInputs(rawTxHex);
}

async parseRawTxBodyStakeInfo(rawTxHex: string) {
const api = await adaWebSdk.getCardanoApi();
return api.parseRawTxBodyStakeInfo(rawTxHex);
}

async extractStakeKeyHashFromBaseAddress(addr: string) {
const api = await adaWebSdk.getCardanoApi();
return api.extractStakeKeyHashFromBaseAddress(addr);
}
}
6 changes: 5 additions & 1 deletion packages/kit-bg/src/providers/ProviderApiCardano.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ class ProviderApiCardano extends ProviderApiBase {
}

@providerApiMethod()
async signTx(request: IJsBridgeMessagePayload, params: { tx: string }) {
async signTx(
request: IJsBridgeMessagePayload,
params: { tx: string; partialSign?: boolean },
) {
defaultLogger.discovery.dapp.dappRequest({ request });
const vault = await this.getAdaVault(request);
if (!vault) {
Expand All @@ -187,6 +190,7 @@ class ProviderApiCardano extends ProviderApiBase {
const encodedTx = await vault.buildTxCborToEncodeTx({
txHex: params.tx,
isSignOnly: true,
partialSign: !!params.partialSign,
});
const result =
await this.backgroundApi.serviceDApp.openSignAndSendTransactionModal({
Expand Down
6 changes: 4 additions & 2 deletions packages/kit-bg/src/services/ServiceSend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -978,15 +978,17 @@ class ServiceSend extends ServiceBase {
networkId: string;
accountId?: string;
memo: string;
tokenAddress?: string;
}) {
const { networkId, accountId, memo } = params;
const { networkId, accountId, memo, tokenAddress } = params;
if (accountId) {
const vault = await vaultFactory.getVault({ networkId, accountId });
return vault.validateMemo(memo);
return vault.validateMemo(memo, tokenAddress);
}

return (await vaultFactory.getChainOnlyVault({ networkId })).validateMemo(
memo,
tokenAddress,
);
}

Expand Down
9 changes: 7 additions & 2 deletions packages/kit-bg/src/vaults/base/VaultBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,14 @@ export abstract class VaultBaseChainOnly extends VaultContext {
/**
* Validate memo/tag field (optional, chain-specific implementation)
* @param memo - The memo string to validate
* @returns Validation result with error message if invalid
* @param tokenAddress - Optional token address for token-aware validation
* (e.g. Stellar contract tokens disallow memo). Undefined when called
* outside of a token-send context (like AddressBook).
*/
async validateMemo(memo: string): Promise<{
async validateMemo(
memo: string,
tokenAddress?: string,
): Promise<{
isValid: boolean;
errorMessage?: string;
}> {
Expand Down
81 changes: 76 additions & 5 deletions packages/kit-bg/src/vaults/impls/ada/KeyringHardware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,11 @@ export class KeyringHardware extends KeyringHardwareBase {

// sign for DApp
if (isSignOnly && rawTxHex) {
const stakingPath = `${dbAccount.path
.split('/')
.slice(0, 4)
.join('/')}/2/0`;
const basePath = dbAccount.path.split('/').slice(0, 4).join('/');
const paymentPath = `${basePath}/0/0`;
const stakingPath = `${basePath}/2/0`;
const keys = {
payment: { hash: null, path: dbAccount.path },
payment: { hash: null, path: paymentPath },
stake: { hash: null, path: stakingPath },
};
cardanoParams = await CardanoApi.txToOneKey(
Expand All @@ -239,6 +238,78 @@ export class KeyringHardware extends KeyringHardwareBase {
dbAccount.xpub,
changeAddress,
);
// The SDK's emitted types declare the mutable fields (`inputs`,
// `additionalWitnessRequests`) as `null` because they're inferred from
// `let x = null`. We view the same object through a structural cast so
// assignments don't need `as any` at every call site.
const mutableCardanoParams = cardanoParams as unknown as {
inputs?: Array<{
prev_hash: string;
prev_index: number;
path?: string;
}>;
additionalWitnessRequests?: string[] | null;
};

// SDK pre-filters encodedTx.inputs to user-owned UTXOs only; firmware
// needs the full inputs set (incl. external contract UTXOs) in the
// original order, otherwise its rebuilt body hash won't match the
// broadcast body hash and the tx is rejected on-chain.
//
// Only user-owned inputs get `path` attached. The wire message sent to
// firmware (CardanoTxInput) only carries prev_hash/prev_index — path is
// an SDK-side hint used by gatherWitnessPaths to decide which witnesses
// to emit. Leaving external/script inputs without a path avoids an
// unneeded paymentPath witness on txs that carry zero user-owned main
// inputs (where forcing one could push past the dApp's fee budget),
// while keeping normal dApp signing unchanged: whenever ≥1 user input
// exists, witnessPaths still contains paymentPath after dedup.
const ownedInputKeys = new Set(
(inputs ?? []).map((u) => `${u.txHash.toLowerCase()}:${u.outputIndex}`),
);
const parsedInputs = await CardanoApi.parseRawTxInputs(rawTxHex);
mutableCardanoParams.inputs = parsedInputs.map((input) => {
const base = {
prev_hash: input.prev_hash,
prev_index: input.prev_index,
};
const key = `${input.prev_hash.toLowerCase()}:${input.prev_index}`;
return ownedInputKeys.has(key) ? { ...base, path: paymentPath } : base;
});

// Drop the stake witness request when the tx clearly doesn't need a
// stake signature (no cert, no withdrawal, user's stake hash not in
// required_signers). The SDK adds it whenever the tx mints, but DeFi
// dApp mints are script-authorized — the extra ~101 bytes can push
// the broadcast tx past the dApp's fee budget (FeeTooSmallUTxO). Any
// parsing failure falls back to keeping the witness.
try {
const additionalWitnessRequests =
mutableCardanoParams.additionalWitnessRequests;
if (
Array.isArray(additionalWitnessRequests) &&
additionalWitnessRequests.includes(stakingPath)
) {
const userStakeKeyHash =
await CardanoApi.extractStakeKeyHashFromBaseAddress(
dbAccount.address,
);
if (userStakeKeyHash) {
const bodyStakeInfo =
await CardanoApi.parseRawTxBodyStakeInfo(rawTxHex);
const stakeWitnessRequired =
bodyStakeInfo.hasCertificates ||
bodyStakeInfo.hasWithdrawals ||
bodyStakeInfo.requiredSignerHashes.includes(userStakeKeyHash);
if (!stakeWitnessRequired) {
mutableCardanoParams.additionalWitnessRequests =
additionalWitnessRequests.filter((p) => p !== stakingPath);
}
}
}
} catch {
// fall through: keep stake witness on any failure
}
} else {
const hasSetTag = await CardanoApi.hasSetTagWithBody(tx.body);
cardanoParams = {
Expand Down
3 changes: 3 additions & 0 deletions packages/kit-bg/src/vaults/impls/ada/Vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,9 +718,11 @@ export default class Vault extends VaultBase {
async buildTxCborToEncodeTx({
txHex,
isSignOnly,
partialSign,
}: {
txHex: string;
isSignOnly: boolean;
partialSign?: boolean;
}): Promise<IEncodedTxAda> {
const dbAccount = (await this.getAccount()) as IDBUtxoAccount;
const changeAddress = getChangeAddress(dbAccount);
Expand Down Expand Up @@ -764,6 +766,7 @@ export default class Vault extends VaultBase {
return {
...encodeTx,
changeAddress,
partialSign,
};
}

Expand Down
24 changes: 22 additions & 2 deletions packages/kit-bg/src/vaults/impls/stellar/Vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,12 @@ export default class Vault extends VaultBase {
// Parse token address to determine if it's classic or contract
const tokenAddressParsed = parseTokenAddress(tokenInfo.address);

if (tokenAddressParsed.type === 'contract') {
if (tokenAddressParsed.type === EStellarAssetType.ContractToken) {
if (memoField) {
throw new OneKeyInternalError({
key: ETranslations.send_stellar_contract_token_no_memo_hint,
});
}
// Contract Token (Soroban Token) transfer
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const contractId = tokenAddressParsed.contractId!;
Expand Down Expand Up @@ -1120,14 +1125,29 @@ export default class Vault extends VaultBase {
return result;
}

override async validateMemo(memo: string): Promise<{
override async validateMemo(
memo: string,
tokenAddress?: string,
): Promise<{
isValid: boolean;
errorMessage?: string;
}> {
if (!memo || !memo.trim()) {
return { isValid: true }; // Empty memo is valid
}

if (tokenAddress) {
const parsed = parseTokenAddress(tokenAddress);
if (parsed.type === EStellarAssetType.ContractToken) {
return {
isValid: false,
errorMessage: appLocale.intl.formatMessage({
id: ETranslations.send_stellar_contract_token_no_memo_hint,
}),
};
}
}

const trimmed = memo.trim();

// Text memo: check byte length
Expand Down
1 change: 1 addition & 0 deletions packages/kit-bg/src/vaults/impls/xrp/Vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ export default class Vault extends VaultBase {

override async validateMemo(
memo: string,
_tokenAddress?: string,
): Promise<{ isValid: boolean; errorMessage?: string }> {
if (!memo) return { isValid: true };

Expand Down
Loading
Loading