@silencelaboratories/walletprovider-sdk
    Preparing search index...

    @silencelaboratories/walletprovider-sdk

    walletprovider-sdk

    The client library for Silent Network Wallet Provider Service.

    Installing

    npm i @silencelaboratories/walletprovider-sdk
    

    Quick start

    We provide simple demo-page what showcases the features of this SDK. Check the demo for a quick start guide.

    Documentation

    For description of classes, interfaces, types, please refer to documentation.

    SDK Usage

    Requests to the network can be authenticated by the User, using Externally Owned Account (EOA), by Passkeys, some of the requests, by session keys (also known as ephemeral keys).

    • Wallet-based authenticate users using their digital wallet.
    • Passkey has 2 steps: register and login. The user registers a passkey with the network, then authenticates request in with the Passkey.
    • Ephemeral keys Used to issue requests to the network and not prompt the user for the acceptance, contains expiry, and can be (self-)revoked.

    The authentication can be required, or not, depending on the version of Silent Network backend.

    In no-authentication case It's backend responsibility to authenticate the User, and then forward the request to the Silent Network nodes.

    For more information, please read Types of architectures in our high-level documentation.

    This SDK supports both versions of Silent Network, no-authentication, and where User authentication is required.

    The request goes to the backend, without any sort of User authentication.

    Create the NoAuthWalletProviderServiceClient, using ClientConfig. The wpClient will connect to the Wallet Provider Backend Service (WPBE).

    const config = {
    walletProviderUrl: "https://silent-network-no-auth.sandbox.silencelaboratories.com/wpbe1",
    apiVersion: 'v2',
    } as ClientConfig;

    const wpClient = new NoAuthWalletProviderServiceClient(clientConfig);
    const sdk = new NetworkSigner(wpClient);

    Following snippet will create client to wpbe1 on our silent-network-no-auth sandbox, and will use v2 endpoints.

    Then pass the wpClient to the NetworkSigner. And use NetworkSigner for interacting with the WPBE.

    During keygen provide number of parties n, the threshold t and types of the keys you want to generate.

    Example usage

     const response = await sdk.generateKey(
    config.threshold,
    config.parties,
    keySignAlgs,
    );

    The message to be signed can be of different types. The example usage of different types is shown here.

    Example usage

      let sample_message = ...;

    const response = await sdk.signMessage(
    config.threshold,
    config.keyId,
    config.signAlg,
    sample_message,
    );

    Create the WalletProviderServiceClient, using ClientConfig. The wpClient will connect to the Wallet Provider Backend Service (WPBE).

      const config = {
    walletProviderUrl: "https://silent-network-auth.sandbox.silencelaboratories.com/wpbe1",
    apiVersion: 'v1',
    } as ClientConfig;

    const wpClient = new WalletProviderServiceClient(clientConfig);

    Following snippet will create client to our wpbe1 on our silent-network-auth sandbox, and will use v1 endpoints.

    The full working example is in the demo.

    Apart from wpClient that connects to the Backend part of the SDK, you are going to need authenticator module.

    We provide EOA authentication via EOAAuth module. Let's create the NetworkSigner with associated EOAAuth object.

      // Authenticate using EOA
    const eoaAuth = new EOAAuth(accountsFromBrowserWallet[0], new BrowserWallet());

    // Create a new signer instance
    const sdk = new NetworkSigner(wpClient, eoaAuth);

    Now you can generate a key by calling the generateKey method. See Policy if you want to restrict what the generated key is allowed to sign.

    Calling this method will cause the Digital Wallet window to pop up, requesting the User to sign the request.

    The returned response KeygenResponse is a list of DKG results, each contains keyId, publicKey and signAlg. The publicKey is the public part of the key generated by Silent Network. The signAlg is the sign algorithm of the MPC key. Use the keyId in subsequent calls to sign.

    The ephemeral sk key can be later used in subsequent signgen requests for authenticating.

    First, we need to register user passkey to the network.

    Example usage

    const rpConfig: RelyingPartyConfig = {
    rpId: clusterConfig.rpConfig.rpId,
    rpName: clusterConfig.rpConfig.rpName,
    };
    userId = newUser();
    const passkeyUser = {
    id: userId,
    displayName: 'Alice',
    name: 'alice@gmail.com ' + userId, // For development purposes
    };

    const passkeyAuth = new PasskeyRegister(rpConfig, passkeyUser);
    // Create a new signer instance
    const sdk = new NetworkSigner(wpClient, passkeyAuth);

    // Generate a new key
    let resp: RegisterPasskeyResponse = await sdk.registerPasskey();

    We provide Passkey login authentication via PasskeyAuth module. Let's create the NetworkSigner with associated PasskeyAuth object.

    Example usage

    const credentialId = getPasskeyCredentialId();
    if (!credentialId) {
    throw new Error('Must register passkey first');
    }
    const passkeyAuth = new PasskeyAuth(
    rpConfig,
    credentialId,
    );

    // Create a new signer instance
    const sdk = new NetworkSigner(wpClient, passkeyAuth);

    // Generate a new key
    const sk = generateEphPrivateKey(selectedEphSignAlg);
    const ephPK = getEphPublicKey(sk, selectedEphSignAlg);
    const ephId = uuidv4();

    const ephClaim = new EphKeyClaim(ephId, ephPK, selectedEphSignAlg, expireAt(60 * 60));
    let resp: KeygenResponse[] = await sdk.generateKey(+threshold, +partiesNumber, signAlgs, ephClaim);

    Now you can generate a key like in the EOA example by calling the generateKey method.

    Calling this method will prompt the device to request Passkey User Verification. Once user verification is done, the KeygenResponse is returned.

    The sk key can be later used in subsequent signgen requests.

    Use a Policy when you want a key to sign only a constrained set of requests.

    Typical uses for policies:

    • Allow transfers only to specific recipients.
    • Cap the maximum amount for ERC-20, native-token, or SPL transfers.
    • Restrict signing to a specific contract function or chain.
    • Allow only specific EIP-191 messages or EIP-712 payloads.

    If you do not provide a policy, the key is created without signing restrictions.

    Policies are built from Policy, Rule, ChainType, TransactionType, TransactionAttribute, Operator, and optionally Logic.

    The structure is:

    • A Policy contains one or more ordered rules.
    • A Rule targets a chain and defines the conditions that must match.
    • A condition checks one transaction attribute, such as receiver, amount, chain ID, or message.
    • A condition group can share an ABI so the policy engine can decode calldata and validate function arguments such as to or value.

    Here is a common import set:

    import {
    Policy,
    Rule,
    ChainType,
    Logic,
    Operator,
    TransactionType,
    TransactionAttribute,
    } from '@silencelaboratories/walletprovider-sdk';

    Example: allow only ERC-20 transfer() calls to a specific token contract, a specific recipient, and an amount below 10000.

    const erc20TransferPolicy = new Policy({
    version: '1.0',
    description: 'Simple ERC20 transfer policy',
    rules: [
    new Rule({
    description: 'Allow transfer() to one recipient with value < 10000',
    chain_type: ChainType.Ethereum,
    conditions: [
    {
    logic: Logic.And,
    abi: {
    name: 'transfer',
    type: 'function',
    inputs: [
    { name: 'to', type: 'address' },
    { name: 'value', type: 'uint256' },
    ],
    outputs: [{ name: '', type: 'bool' }],
    },
    group: [
    {
    transaction_type: TransactionType.Erc20,
    transaction_attr: TransactionAttribute.Receiver,
    operator: Operator.Eq,
    value: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238',
    },
    {
    transaction_type: TransactionType.Erc20,
    transaction_attr: 'to',
    operator: Operator.Eq,
    value: '0x1758f42af7026fbbb559dc60ece0de3ef81f665e',
    },
    {
    transaction_type: TransactionType.Erc20,
    transaction_attr: 'value',
    operator: Operator.Lt,
    value: 10000,
    },
    ],
    },
    ],
    }),
    ],
    });

    Example: allow a Solana native transfer only to one recipient and only if the amount is at most 100.

    const solTransferPolicy = new Policy({
    version: '1.0',
    description: 'Solana transfer policy',
    rules: [
    new Rule({
    description: 'Allow transfer to one address with value <= 100',
    chain_type: ChainType.Solana,
    conditions: [
    {
    transaction_type: TransactionType.NativeTransfer,
    transaction_attr: TransactionAttribute.Receiver,
    operator: Operator.Eq,
    value: 'DGUiWE2kY5rEhPNrwCGygcyrWwLxJFrH7ApFep6A8rdF',
    },
    {
    transaction_type: TransactionType.NativeTransfer,
    transaction_attr: TransactionAttribute.NativeValue,
    operator: Operator.Lte,
    value: 100,
    },
    ],
    }),
    ],
    });

    You can attach a policy during key generation:

    const signAlgs = ['secp256k1', 'ed25519'];

    const selectedEphSignAlg = 'secp256k1';
    const sk = generateEphPrivateKey(selectedEphSignAlg);
    const ephPK = getEphPublicKey(sk, selectedEphSignAlg);
    const ephId = uuidv4();
    const ephClaim = new EphKeyClaim(ephId, ephPK, selectedEphSignAlg, expireAt(60 * 60));

    const resp: KeygenResponse[] = await sdk.generateKey(
    +threshold,
    +partiesNumber,
    signAlgs,
    ephClaim,
    erc20TransferPolicy,
    );

    You can also change or remove the policy later for an existing key:

    await sdk.updatePolicy(selectedKeyId, solTransferPolicy);
    await sdk.deletePolicy(selectedKeyId);

    In practice, a good pattern is to start with the smallest policy that supports your flow, test the exact transaction payloads your app produces, and only then broaden the rules if needed.

    The full signing example is here.

    The workflow is similar to the keygen process. The core objects to use are the NetworkSigner, WalletProviderServiceClient, and the ephemeral authenticator module.

    const authModule = new EphAuth(selectedEphId, ephSK, selectedEphSignAlg);
    // Create a new signer instance
    const sdk = new NetworkSigner(wpClient, authModule);

    Use the SignRequestBuilder builder to generate the sign message payload. Then call the signMessage method to run the signing process.

    const data = encodeFunctionData({
    abi: ERC_20_ABI,
    functionName: 'transfer',
    args: ['0x1758f42af7026fbbb559dc60ece0de3ef81f665e', 1000n],
    });

    const eip1559Tx: UnsignedTransactionRequest = {
    from: ethAddress,
    to: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238', // USDC Contract
    value: '0x0',
    data,
    chainId: 80002,
    nonce: 0,
    gas: '0x18473',
    maxFeePerGas: '0x18473',
    maxPriorityFeePerGas: '0x1000',
    };
    const signMessage = new SignRequestBuilder()
    .setRequest(
    uuidv4(),
    JSON.stringify(eip1559Tx),
    'EIP1559',
    )
    .setRequest(
    uuidv4(),
    '4549502d313931206d657373616765',
    'rawBytes',
    )
    .build();

    let resp = await sdk.signMessage(threshold, selectedKeyId, 'secp256k1', signMessage);

    The SignResponse contains the signature sign, the recovery ID recid and the transaction ID transactionId.

    The full key refresh example is here.

    The workflow is similar to the keygen process.

    const algSign = 'secp256k1'; // Signing algorithms of Ephemeral Key

    // Create EOA authenticator
    const eoaAuth = new EOAAuth(
    accountsFromBrowserWallet[0],
    new BrowserWallet(),
    );

    // Create a new signer instance
    const sdk = new NetworkSigner(wpClient, eoaAuth);

    Now you can refresh the key (before doing this, make sure you've already generated the key), using the refreshKey method.

    // Refresh the key
    let resp: KeyRefreshResponse = await sdk.refreshKey(+threshold, selectedKeyId, mpcKeySignAlg);

    The returned response KeyRefreshResponse contains keyId, publicKey and signAlg of the refreshed MPC key.

    Development

    npm i
    npm run build

    The output will be in the dist folder.

    Please refer to README.md for instructions how to execute them.

    npm run docs
    
    ./local_ci.sh