PartyLayerDocs
Try Demo

Send Wallet

Send is a passkey-based Canton wallet that speaks the splice-wallet-kernel OpenRPC contract and announces itself over canton:announceProvider rather than binding the shared window.canton slot. The dApp connection layer is open-sourced as Sigilry; this PartyLayer adapter wraps that contract with the same surface every other Canton wallet uses.

How Send Differs

PropertySendNotes
AuthenticationPasskey (WebAuthn-PRF)Touch ID / Face ID prompt per signature
Discoverycanton:announceProviderAnnounce-based (EIP-6963 style); does not bind window.canton
Networkscanton:mainnet only

Installation

Send is delivered as a browser extension. Direct your users to the Send wallet homepage at sigilry.org for current installation instructions before they can connect.

bash
npm install @partylayer/sdk @partylayer/react @partylayer/adapter-send

With PartyLayerKit there is no further wiring: Send is served through the CIP-0103 announce path, so it appears in the wallet picker automatically when the user has Send installed. If you build a custom adapter set and want the bespoke SendAdapter explicitly, register it alongside the built-ins:

tsx
import { createPartyLayer, getBuiltinAdapters, SendAdapter } from '@partylayer/sdk';

const client = createPartyLayer({
  network: 'mainnet',
  app: { name: 'My dApp' },
  adapters: [...getBuiltinAdapters(), new SendAdapter()],
});
ℹ️ Note
Send is not in getBuiltinAdapters() (that returns Console, Loop, Cantor8, and Nightly). By default Send is discovered through the CIP-0103 announce path, so the explicit SendAdapter above is only needed if you want the bespoke adapter instead of the announce path.

Connection Flow

tsx
import { useConnect } from '@partylayer/react';

function ConnectWithSend() {
  const { connect, isConnecting } = useConnect();
  return (
    <button
      onClick={() => connect('send')}
      disabled={isConnecting}
    >
      {isConnecting ? 'Connecting…' : 'Connect with Send'}
    </button>
  );
}

End-to-end user experience:

  • User clicks Connect with Send.
  • Send extension shows its Connect to Site? permission prompt.
  • On approval, the OS surfaces a passkey prompt (Touch ID / Face ID).
  • Once unlocked, the SDK receives a session containing partyId, kernelId, and the wallet's public key.
ℹ️ Note
Every signature operation (signMessage, submitTransaction) prompts a fresh passkey unlock — this is by design. Send does not cache passkey approval across calls.

Reading the Ledger

Send proxies the Canton v2 JSON Ledger API through Sigilry's ledgerApi RPC method. Use useLedgerApi for any read-side query:

tsx
import { useLedgerApi } from '@partylayer/react';

function LedgerEndDisplay() {
  const { ledgerApi } = useLedgerApi();
  const [offset, setOffset] = useState<string | null>(null);

  useEffect(() => {
    ledgerApi({
      requestMethod: 'GET',
      resource: '/v2/state/ledger-end',
    }).then(({ response }) => {
      const parsed = JSON.parse(response) as { offset: string };
      setOffset(parsed.offset);
    });
  }, [ledgerApi]);

  return <div>Ledger end: {offset ?? 'loading…'}</div>;
}

For active-contracts queries with eventFormat, see the Wallet Balances guide — Send accepts the same request shape as the other ledger-API-capable adapters.

Token Standard Transfers (CIP-56)

Send signs and submits transactions in a single step via prepareExecuteAndWait. The wallet handles Scan-side coordination, choice context lookup, and passkey signing internally. Adapter consumers call submitTransaction with a JsPrepareSubmissionRequest:

tsx
import { useSubmitTransaction } from '@partylayer/react';

const { submit } = useSubmitTransaction();

await submit({
  signedTx: {
    commandId: crypto.randomUUID(),
    commands: [
      {
        ExerciseCommand: {
          templateId:
            '#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory',
          contractId: factoryCid,
          choice: 'TransferFactory_Transfer',
          choiceArgument: { /* …Scan-derived shape… */ },
        },
      },
    ],
    actAs: [session.partyId],
  },
});

See Token Transfers for the full CIP-56 flow including the Scan /registry/transfer-instruction/v1/transfer-factory endpoint and the choice-context tagged-union shape.

⚠️ Warning
The Send adapter ships the same templateId migration warning as Loop — passing a legacy Amulet_Transfer exercise on Splice.Amulet:Amulet throws an actionable error pointing at this page.

Capability Matrix

  • connect — supported (Sigilry connect RPC + getPrimaryAccount)
  • disconnect — supported
  • restore — supported (silent status probe; no popup on reload)
  • signMessage — supported (passkey-signed)
  • signTransactionnot supported; fused into prepareExecute. Calling it throws CapabilityNotSupportedError pointing at submitTransaction.
  • submitTransaction — supported via prepareExecuteAndWait; receipt populated from tx.payload.updateId.
  • ledgerApi — supported (full Sigilry passthrough; matches Console / Nightly).
  • events — supported; txChanged bridged to PartyLayer tx:status.
  • injected: declared as a capability, but Send is discovered and driven over the canton:announceProvider channel rather than by binding the shared window.canton slot.

Network Support

This adapter integrates with Send on canton:mainnet.

ℹ️ Note
Demo-app display caveat: the PartyLayer demo at localhost:3000 defaults its network label to devnet. That label reflects the demo's configuration — not the actual network the connected wallet sits on. Send's adapter reads the live network via its getActiveNetwork() provider call and reports canton:mainnet when Send is active. dApps that ship to production should configure PartyLayerKit with network="mainnet" when targeting Send.

Troubleshooting

  • "Send not detected": the extension is missing, or Send did not announce over canton:announceProvider within the detection window. Send announces on its own channel and does not depend on owning window.canton; install Send and reload.
  • "Connection cancelled" — the user dismissed the passkey prompt or the extension popup. Triggering connect again is safe.
  • "Authentication Failed: Cannot reach authentication server" — Send's backend at auth.cantonwallet.com is unreachable. Check network and retry.
  • "OAuth state mismatch" — stale Send session. Clear cookies for cantonwallet.com and reconnect.
  • Transaction errors with hint "Execute Unknown on Unknown" — legacy Amulet_Transfer exercise on Splice.Amulet:Amulet. Migrate to CIP-56 TransferFactory_Transfer; see Token Transfers.

Security Notes

  • Private keys never leave the extension. Passkey signing happens on the user's device through WebAuthn-PRF; PartyLayer never touches the underlying key material.
  • Session JWT is held by the extension. The adapter receives an access token in status.session.accessToken for the lifetime of the connection; PartyLayer's session-persistence layer encrypts state at rest in the dApp's configured storage.
  • Announce-scoped channel. Send is discovered and every RPC is routed over the dedicated canton:announceProvider channel that Send advertises. Because the channel is bound to the announcing extension, the adapter never acts on a foreign provider that happens to own window.canton; if Send does not announce, the adapter cleanly returns not installed and yields to the matching adapter.

References

PreviousWallets & AdaptersNextCIP-0103 Provider