Skip to content
CTRL K
    Wallet adapter

    Wallet adapter

    Write operations need a signature from your wallet. Nutrimatic does not ship a wallet product and does not manage connections, sessions, or account pickers.

    You implement a small WalletAdapter and pass it in SDK config when your app runs writes (and optionally so portfolio reads can omit owner). The SDK asks for signatures via SignRequest; your adapter returns a SignedPayload. A multi-step plan may call sign more than once — Settlement — Signing across steps. Init: Configuration — Modes.

    Nutrimatic prepares what must be signed; your adapter asks the user’s wallet (MetaMask, WalletConnect, custodial signer, …) to sign that payload and returns the result. You do not build swap calldata yourself.

    What you implement vs what Nutrimatic does

    YouNutrimatic SDK
    Connect MetaMask, WalletConnect, a custodial signer, etc.Request a typed SignRequest for the current operation
    Show connect / account UIPass the signed result so Nutrimatic can execute
    Map chain switches and user rejection to your UXSurface stable error codes (e.g. USER_REJECTED)

    Config

    import { createNutrimaticSdk } from '@nutrimatic/sdk'
    
    const sdk = createNutrimaticSdk({
      ledgers: [{ id: 'evm:421018', kind: 'evm', chainId: 421018, alias: 'infinite-mainnet' }],
      mode: 'full',
      integrationContext: 'client',
      wallet: myWalletAdapter,
    })

    wallet is optional at the type level; write methods that require signing fail if it is missing when the operation needs it. See Configuration.

    Browser wallet (EVM) — what you wire

    Nutrimatic does not ship MetaMask. You connect the wallet in your UI, then implement WalletAdapter so the SDK can ask for signatures.

    Typical flow:

    1. Connect — your app asks the browser wallet for accounts (e.g. MetaMask eth_requestAccounts).
    2. getAddress / getLedger — return the active account and the Nutrimatic ledger id for the chain the user selected (e.g. evm:421018 for Infinite Mainnet).
    3. sign — when the SDK calls sign(request), if request.kind is 'evm', forward request.payload to the wallet with the RPC your library uses for that payload shape (often typed-data or transaction signing). Return the signed material in SignedPayload.
    4. If the user rejects in the wallet UI, surface that as USER_REJECTED (or let your library’s rejection map to it).
    // Shape only — use your wallet library’s APIs for connect + sign.
    const browserWallet: WalletAdapter = {
      async getAddress() {
        return currentAccountAddress // from your connect flow
      },
      async getLedger() {
        return 'evm:421018' // map the wallet’s chain to a Nutrimatic ledger id
      },
      async sign(request) {
        if (request.kind !== 'evm') {
          throw new Error(`Unsupported ledger kind: ${request.kind}`)
        }
        // If request.expiresAt is set (Unix ms), do not sign after that time
        const signed = await walletLibrary.signNutrimaticEvmPayload(request.payload)
        return { ledger: request.ledger, kind: request.kind, payload: signed }
      },
    }

    You still own connect buttons, account switching, and chain switching. The SDK only calls these three methods when a write (or a default owner) needs them.

    WalletAdapter

    Do not assume every request is an EVM transaction — ledger / kind tell you which network family the payload belongs to.

    interface WalletAdapter {
      getAddress(): Promise<string>
      getLedger(): Promise<LedgerId>
      sign(request: SignRequest): Promise<SignedPayload>
    }
    MethodDescription
    getAddress()Address / account the user is acting as on the current ledger family.
    getLedger()Canonical ledger ID currently selected in the wallet (never an alias).
    sign(request)Sign the opaque SignRequest.payload for that ledger family and return a SignedPayload.

    SignRequest

    interface SignRequest {
      readonly ledger: LedgerId
      readonly kind: 'evm' | 'bitcoin' | 'cosmos' | string
      readonly purpose:
        | 'swap'
        | 'liquidity_add'
        | 'liquidity_remove'
        | 'settlement'
        | 'approve'
        | 'stake'
        | 'unstake'
        | 'stake_claim'
        | 'farm_deposit'
        | 'farm_withdraw'
        | 'farm_harvest'
        | string
      readonly payload: unknown
      readonly expiresAt?: number
    }
    FieldDescription
    ledgerCanonical ledger ID this signature is for.
    kindNetwork family (evm, bitcoin, cosmos, …). Your adapter uses this to interpret payload.
    purposeWhy the SDK is asking (swap, approve, stake, farm_deposit, …). Useful for UI copy.
    payloadOpaque signing input for that family — sign what Nutrimatic sends; do not rebuild the operation yourself.
    expiresAtOptional. Unix time in milliseconds after which you should not sign (treat as expired).

    Example (shape your adapter receives — payload stays opaque):

    {
      "ledger": "evm:421018",
      "kind": "evm",
      "purpose": "swap",
      "payload": { "/* ledger-native signing input */": true },
      "expiresAt": 1719799200000
    }

    Treat payload as opaque unless your adapter knows how to handle that kind. Sign what the SDK provides; do not rebuild the operation yourself.

    SignedPayload

    interface SignedPayload {
      readonly ledger: LedgerId
      readonly kind: string
      readonly payload: unknown
    }

    Example (what your adapter returns):

    {
      "ledger": "evm:421018",
      "kind": "evm",
      "payload": { "/* signed material for that family */": true }
    }

    Return the signed material in the form your ledger family expects. The SDK submits it for execution.

    Minimal mock (tests / local)

    const mockWallet: WalletAdapter = {
      async getAddress() {
        return '0x…'
      },
      async getLedger() {
        return 'evm:421018'
      },
      async sign(request) {
        return {
          ledger: request.ledger,
          kind: request.kind,
          payload: { signed: true, request },
        }
      },
    }

    Errors

    CodeWhen
    WALLET_REQUIREDWrite needs a wallet and none was configured
    USER_REJECTEDUser (or adapter) declined to sign
    QUOTE_EXPIREDUnderlying quote/request expired before sign completed
    READ_ONLY_MODEInit used read-only mode — Modes

    Write operations · Whose wallet (owner) · Swaps · Errors