Skip to content

wallets: merge main into wallets-v1#1659

Open
guilleasz-crossmint wants to merge 10 commits intowallets-v1from
devin/1773862835-merge-wallets-v1-and-main
Open

wallets: merge main into wallets-v1#1659
guilleasz-crossmint wants to merge 10 commits intowallets-v1from
devin/1773862835-merge-wallets-v1-and-main

Conversation

@guilleasz-crossmint
Copy link
Contributor

@guilleasz-crossmint guilleasz-crossmint commented Mar 18, 2026

Description

Merges main into wallets-v1, resolving all merge conflicts. The primary conflict area was integrating main's server signer support into wallets-v1's refactored wallet API (which renamed signerrecovery, delegatedSignerssigners, and split getOrCreateWallet into getWallet/createWallet).

Key conflict resolutions

  • wallet-factory.ts: Integrated server signer address derivation into createWallet, registerSigners, and validateSigners. Server signers are resolved to server:${derivedAddress} format using deriveServerSignerDetails.
  • wallet.ts: Added resolveSigner() helper to handle string | ServerSignerConfig | undefined signer options, unifying device and server signer resolution. Also fixed error messages in submitApprovals that were printing [object Object] instead of pendingApproval.signer.locator.
  • Server signer classes (evm/solana/stellar): Added SignerLocator type cast on locator() return to satisfy the Signer interface.
  • signers/types.ts: Added ServerInternalSignerConfig to the InternalSignerConfig union type.
  • activity.tsx (quickstart-devkit): Fixed demo app to use .events instead of .data to match the actual WalletsActivityResponseUnstableDto shape from the merged openapi types. The wallets-v1 branch had introduced a .data property that doesn't exist on this DTO.

Items for careful review

  1. as unknown as ServerSignerConfig casts in validateSigners and registerSigners — these bypass type safety because SignerConfigForChain<C> doesn't directly include the secret field. Consider whether ServerSignerConfig should be part of SignerConfigForChain.
  2. compareSignerConfigs is skipped for server signers — the wallet API returns server signers as "external-wallet" type, so field-by-field comparison fails on type. Locator matching is used instead. Verify this is sufficient validation.
  3. getWallet no longer validates server signer address mismatch — in wallets-v1, getWallet takes WalletArgsFor (no recovery), so the server signer address check from main doesn't apply. The corresponding test was updated to expect success instead of rejection. Confirm this is acceptable behavior.
  4. Activity component field mapping — changed from transfer.sender.address / transfer.recipient.address / transfer.token.amount to event.from_address / event.to_address / event.amount. These match the generated WalletsActivityResponseUnstableDto type but should be verified against actual API responses at runtime.

Test plan

  • All existing unit tests pass (pnpm test:vitest — 225 passed, 68 skipped)
  • Build passes (pnpm build:libs)
  • Lint passes (pnpm lint)
  • CI passes (lint, build & test, smoke tests)
  • Server signer test "should resolve server signer to server:<derivedAddress> in signers" validates the new registerSigners and validateSigners logic
  • Test for getWallet with mismatched server signer address updated to reflect wallets-v1 API behavior

Package updates

Includes version bumps and changelogs from main for multiple packages (client-sdk-base, client-sdk-react-ui, server-sdk, wallets-sdk, etc.). Also includes @basis-theory-ai/react@basis-theory/react-agentic rename and @noble/hashes version unification to 1.8.0 from main. No new changeset added for the merge conflict resolution itself — may need one if this counts as a functional change to @crossmint/wallets-sdk.

Link to Devin session: https://crossmint.devinenterprise.com/sessions/33b6ec310a5b4bb59b292da3dae4c515
Requested by: @guilleasz-crossmint


Open with Devin

github-actions bot and others added 8 commits March 13, 2026 11:52
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* a

* change

* fix
…ated orders (#1647)

* Add orderId + clientSecret support to hosted checkout

* Simplify URL param appending for hosted checkout v3

* Make fiat and crypto payment configs optional for existing orders

* Encode orderId in URL path to handle special characters securely
* Feat: add server key signers

* added solana and stellar support, cleaned up changes

* added changeset

* added tests

* update lockfile

* lint fix

* address Greptile review comments: hex validation, signer checks, docs

Co-Authored-By: Jonathan Derbyshire <jonathan.temp@paella.dev>

* fix: replace any cast with unknown, add base64 validation for stellar signer

Co-Authored-By: Jonathan Derbyshire <jonathan.temp@paella.dev>

* fix: enforce 64-char minimum secret length in key derivation

Co-Authored-By: Jonathan Derbyshire <jonathan.temp@paella.dev>

* fixed txn signing, and cleanup

* fix lint

* fix tests

* lint fix

* revert some signer wallet logic and cleanup

* reverted alias stuff

* cleanup

* add pragmatic server side env check

* fix messed up previous commits

* update schema to assume backend wallet changes

* fix lint

* update schema to align with backend changes

* add server signer type to delegate signer

* lint fix

* improved delegated signer check for server signers

* review feedback

* reverted type in openapi.json

* fix type issue breaking change revert

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Resolved conflicts in 11 files:
- packages/wallets/src/api/__tests__/test-utils.ts
- packages/wallets/src/api/types.ts
- packages/wallets/src/openapi.json
- packages/wallets/src/signers/types.ts
- packages/wallets/src/wallets/evm.ts
- packages/wallets/src/wallets/solana.ts
- packages/wallets/src/wallets/stellar.ts
- packages/wallets/src/wallets/types.ts
- packages/wallets/src/wallets/wallet-factory.test.ts
- packages/wallets/src/wallets/wallet-factory.ts
- packages/wallets/src/wallets/wallet.ts

Merge strategy:
- Keep wallets-v1 API surface (signer instead of experimental_signer, recovery instead of signer for admin)
- Integrate main's server signer support into wallets-v1's architecture
- Add resolveSigner helper to Wallet class for transparent device/server signer handling
- Update tests to use new API conventions

Co-Authored-By: Guille <guille.a@paella.dev>
…et factory

- Add server signer handling to validateSigners() with deriveServerSignerDetails
- Add server signer handling to registerSigners() to resolve server:derivedAddress locators
- Skip compareSignerConfigs for server signers (API returns external-wallet type)
- Fix server signer locator return type in evm/solana/stellar server signers
- Fix missing DeviceSignerKeyStorage import and syntax error in wallet-factory
- Update test for getWallet server signer validation (not applicable in wallets-v1 API)

Co-Authored-By: Guille <guille.a@paella.dev>
@devin-ai-integration
Copy link
Contributor

Original prompt from Guille

SYSTEM:
=== BEGIN THREAD HISTORY ===
Guillermo Aszyn (U06MHK4CG0Y): @Devin créate a new branch for merging wallets-v1 and main together in Crossmint-sdk, solve the merge conflicts, if doubts ask me
=== END THREAD HISTORY ===

Thread URL: https://crossmint.slack.com/archives/D085SCF9469/p1773862650910959?thread_ts=1773862650.910959&amp;cid=D085SCF9469

The latest message is the one right above that tagged you.

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@changeset-bot
Copy link

changeset-bot bot commented Mar 18, 2026

⚠️ No Changeset found

Latest commit: 024e6e8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

devin-ai-integration bot and others added 2 commits March 18, 2026 21:14
…ivityResponseUnstableDto

The Transfers type (WalletsActivityResponseUnstableDto) has .events not .data.
Updated the quickstart-devkit demo app to use the correct property names.

Co-Authored-By: Guille <guille.a@paella.dev>
Co-Authored-By: Guille <guille.a@paella.dev>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

⚠️ 1 issue in files not directly in the diff

⚠️ getSignerLocator has no handler for server signer type, falls through to generic return (packages/wallets/src/utils/signer-locator.ts:33)

In signer-locator.ts:33, getSignerLocator has no case for type === "server". If a ServerSignerConfig reaches this function (e.g., if the chain != null guard in wallet-factory.ts:382 is somehow bypassed), it would fall through to return signer.type as SignerLocator which returns the string "server" — an invalid signer locator that would cause API errors. While the current call site in registerSigners guards with chain != null, this is fragile since getSignerLocator is a shared utility that could be called from other locations.

View 5 additional findings in Devin Review.

Open in Devin Review

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 buildInternalSignerConfig throws for server signer type in recovery path

In wallet.ts:1044-1045, the buildInternalSignerConfig switch has no case for "server" and hits the default throw new Error("Unknown signer type: server"). When createWalletInstance (wallet-factory.ts:184) passes the API response's admin signer config as recovery to the Wallet constructor, and that signer has type: "server", any code path that calls buildInternalSignerConfig with the recovery config will throw. Currently this is only called for device signers, but it creates a latent incompatibility where server-signer wallets cannot be extended with device signers.

(Refers to lines 1044-1045)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +31 to +32
const { orderId, ...restOfParams } = props as CrossmintHostedCheckoutV3OrderProps;
appendObjectToQueryParams(queryParams, restOfParams);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 clientSecret leaked in URL query parameters for order-based checkout

When using the order-based checkout flow, the clientSecret is included in the URL query string. In crossmintHostedCheckoutV3Service.ts:31, only orderId is destructured out of the props before passing the rest to appendObjectToQueryParams. This means clientSecret is appended as a visible query parameter in the popup/new-tab URL. Query parameters are stored in browser history, can appear in server logs, referrer headers, and browser extensions. The clientSecret should either be excluded from query params or transmitted through a more secure channel (e.g., postMessage after the window opens).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}

async signMessage(message: string) {
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(message)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Stellar base64 validation regex accepts empty strings

In stellar-server-signer.ts:25, the regex /^[A-Za-z0-9+/]*={0,2}$/ matches an empty string because * allows zero characters and ={0,2} allows zero padding. An empty message would pass validation and be signed, producing a signature over an empty buffer, which is likely unintended and could cause downstream issues.

Suggested change
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(message)) {
if (!message || !/^[A-Za-z0-9+/]+={0,2}$/.test(message)) {
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 18, 2026

Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/wallets/src/wallets/wallet-factory.ts
Line: 382-391

Comment:
**Silent fallthrough produces invalid server signer locator**

When `signer.type === "server"` but `chain` is `null` / `undefined`, the condition on line 382 is false and execution falls through to `getSignerLocator(signer)`. `getSignerLocator` has no special handling for `"server"`, so it hits the catch-all `return signer.type as SignerLocator` and returns the bare string `"server"` instead of `"server:<derivedAddress>"`. This silently submits an invalid locator to the API.

While `chain` is always provided in the current `createWallet` call-path, the optional signature (`chain?: C`) allows future callers to inadvertently trigger this. The block should throw an explicit error when `chain` is missing rather than silently falling through:

```suggestion
                    if (signer.type === "server") {
                        if (chain == null) {
                            throw new WalletCreationError(
                                "chain is required to register a server signer"
                            );
                        }
                        const { derivedAddress } = deriveServerSignerDetails(
                            signer as unknown as ServerSignerConfig,
                            chain,
                            this.apiClient.projectId,
                            this.apiClient.environment
                        );
                        return { signer: `server:${derivedAddress}` };
                    }
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/wallets/src/wallets/types.ts
Line: 114-116

Comment:
**Unused type `DelegatedSignerInput`**

`DelegatedSignerInput` is defined here but is not referenced anywhere else in the codebase and is not exported from `packages/wallets/src/index.ts`. Per the project's convention of removing unused code, this type should be removed until it is actually needed.

**Rule Used:** Remove unused code from PRs to keep them lean. Add... ([source](https://app.greptile.com/review/custom-context?memory=83771aea-3c5f-4a37-9170-8ac881cd5efd))

**Learnt From**
[Paella-Labs/crossbit-main#20960](https://github.com/Paella-Labs/crossbit-main/pull/20960)

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/wallets/quickstart-devkit/components/activity.tsx
Line: 56

Comment:
**`token_symbol` is optional — missing fallback**

According to the `WalletsActivityResponseUnstableDTO` OpenAPI schema, `token_symbol` is **not** in the `required` array, meaning it can be absent for some activity events (e.g. unknown or non-standard tokens). The previous code guarded this with `transfer.token.symbol ?? transfer.token.locator`, but the new code renders nothing when `token_symbol` is undefined, leaving a blank next to the amount.

Consider falling back to a meaningful placeholder, for example:

```suggestion
                                    {event.amount} {event.token_symbol ?? ""}
```

or a more informative fallback such as `event.token_symbol ?? event.mint_hash?.slice(0, 8) ?? "Unknown"`.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: lint formatting..."

Comment on lines +382 to 391
if (signer.type === "server" && chain != null) {
const { derivedAddress } = deriveServerSignerDetails(
signer as unknown as ServerSignerConfig,
chain,
this.apiClient.projectId,
this.apiClient.environment
);
return { signer: `server:${derivedAddress}` };
}
return { signer: getSignerLocator(signer) as string };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Silent fallthrough produces invalid server signer locator

When signer.type === "server" but chain is null / undefined, the condition on line 382 is false and execution falls through to getSignerLocator(signer). getSignerLocator has no special handling for "server", so it hits the catch-all return signer.type as SignerLocator and returns the bare string "server" instead of "server:<derivedAddress>". This silently submits an invalid locator to the API.

While chain is always provided in the current createWallet call-path, the optional signature (chain?: C) allows future callers to inadvertently trigger this. The block should throw an explicit error when chain is missing rather than silently falling through:

Suggested change
if (signer.type === "server" && chain != null) {
const { derivedAddress } = deriveServerSignerDetails(
signer as unknown as ServerSignerConfig,
chain,
this.apiClient.projectId,
this.apiClient.environment
);
return { signer: `server:${derivedAddress}` };
}
return { signer: getSignerLocator(signer) as string };
if (signer.type === "server") {
if (chain == null) {
throw new WalletCreationError(
"chain is required to register a server signer"
);
}
const { derivedAddress } = deriveServerSignerDetails(
signer as unknown as ServerSignerConfig,
chain,
this.apiClient.projectId,
this.apiClient.environment
);
return { signer: `server:${derivedAddress}` };
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/wallets/src/wallets/wallet-factory.ts
Line: 382-391

Comment:
**Silent fallthrough produces invalid server signer locator**

When `signer.type === "server"` but `chain` is `null` / `undefined`, the condition on line 382 is false and execution falls through to `getSignerLocator(signer)`. `getSignerLocator` has no special handling for `"server"`, so it hits the catch-all `return signer.type as SignerLocator` and returns the bare string `"server"` instead of `"server:<derivedAddress>"`. This silently submits an invalid locator to the API.

While `chain` is always provided in the current `createWallet` call-path, the optional signature (`chain?: C`) allows future callers to inadvertently trigger this. The block should throw an explicit error when `chain` is missing rather than silently falling through:

```suggestion
                    if (signer.type === "server") {
                        if (chain == null) {
                            throw new WalletCreationError(
                                "chain is required to register a server signer"
                            );
                        }
                        const { derivedAddress } = deriveServerSignerDetails(
                            signer as unknown as ServerSignerConfig,
                            chain,
                            this.apiClient.projectId,
                            this.apiClient.environment
                        );
                        return { signer: `server:${derivedAddress}` };
                    }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +114 to +116
export type DelegatedSignerInput = {
signer: string | ServerSignerConfig;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused type DelegatedSignerInput

DelegatedSignerInput is defined here but is not referenced anywhere else in the codebase and is not exported from packages/wallets/src/index.ts. Per the project's convention of removing unused code, this type should be removed until it is actually needed.

Rule Used: Remove unused code from PRs to keep them lean. Add... (source)

Learnt From
Paella-Labs/crossbit-main#20960

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/wallets/src/wallets/types.ts
Line: 114-116

Comment:
**Unused type `DelegatedSignerInput`**

`DelegatedSignerInput` is defined here but is not referenced anywhere else in the codebase and is not exported from `packages/wallets/src/index.ts`. Per the project's convention of removing unused code, this type should be removed until it is actually needed.

**Rule Used:** Remove unused code from PRs to keep them lean. Add... ([source](https://app.greptile.com/review/custom-context?memory=83771aea-3c5f-4a37-9170-8ac881cd5efd))

**Learnt From**
[Paella-Labs/crossbit-main#20960](https://github.com/Paella-Labs/crossbit-main/pull/20960)

How can I resolve this? If you propose a fix, please make it concise.

<div>
<div className="font-medium">
{transfer.token.amount} {transfer.token.symbol ?? transfer.token.locator}
{event.amount} {event.token_symbol}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 token_symbol is optional — missing fallback

According to the WalletsActivityResponseUnstableDTO OpenAPI schema, token_symbol is not in the required array, meaning it can be absent for some activity events (e.g. unknown or non-standard tokens). The previous code guarded this with transfer.token.symbol ?? transfer.token.locator, but the new code renders nothing when token_symbol is undefined, leaving a blank next to the amount.

Consider falling back to a meaningful placeholder, for example:

Suggested change
{event.amount} {event.token_symbol}
{event.amount} {event.token_symbol ?? ""}

or a more informative fallback such as event.token_symbol ?? event.mint_hash?.slice(0, 8) ?? "Unknown".

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/wallets/quickstart-devkit/components/activity.tsx
Line: 56

Comment:
**`token_symbol` is optional — missing fallback**

According to the `WalletsActivityResponseUnstableDTO` OpenAPI schema, `token_symbol` is **not** in the `required` array, meaning it can be absent for some activity events (e.g. unknown or non-standard tokens). The previous code guarded this with `transfer.token.symbol ?? transfer.token.locator`, but the new code renders nothing when `token_symbol` is undefined, leaving a blank next to the amount.

Consider falling back to a meaningful placeholder, for example:

```suggestion
                                    {event.amount} {event.token_symbol ?? ""}
```

or a more informative fallback such as `event.token_symbol ?? event.mint_hash?.slice(0, 8) ?? "Unknown"`.

How can I resolve this? If you propose a fix, please make it concise.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

🔥 Smoke Test Results

Status: Passed

Statistics

  • Total Tests: 5
  • Passed: 5 ✅
  • Failed: 0
  • Skipped: 0
  • Duration: 3.45 min

✅ All smoke tests passed!

All critical flows are working correctly.


This is a non-blocking smoke test. Full regression tests run separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants