A starter template for building Internet Computer (IC) applications with integrated authentication using the @windoge98/plug-n-play
wallet adapter. This template uses SvelteKit for the frontend and uses Rust for the backend.
src/
āāā declarations/ # Generated TypeScript interfaces
āāā pnp_skeleton_backend/ # Backend canister code
ā āāā src/
ā āāā authentication.rs # Authentication standards implementation (ICRC-21, ICRC-34, ICRC-28)
āāā pnp_skeleton_frontend/ # Frontend canister code
āāā src/
āāā lib/
ā āāā components/ # Reusable components (including SignIn)
ā āāā config/ # Configuration files including auth.config.ts
ā āāā stores/ # Svelte stores including auth.ts
āāā routes/ # Application routes
āāā +page.svelte # Main page with greeting form
āāā auth/ # Authentication pages
Clone this repository:
git clone <repository-url>
cd pnp_sveltekit_skeleton
Install dependencies:
npm install
Start the local Internet Computer replica:
dfx start --clean --background
Deploy the canisters locally:
dfx deploy
The authentication system is configured in src/pnp_skeleton_frontend/src/lib/config/auth.config.ts
. This file initializes the Plug-n-Play system with various wallet adapters.
Key configuration options:
adapters
: Which authentication providers to enable/disablederivationOrigin
: The host url used to derive the principal ID, this is handled automatically to point to your raw canister url, only override if you know what you're doingdelegationTargets
: Canister IDs that the frontend is allowed to call on the user's behalflocalIdentityCanisterId
: Internet Identity canister ID for local developmentsiwsProviderCanisterId
: The "Sign in with Solana" provider canister ID for local developmentFor WalletConnect integration, you'll need to:
projectId
in the walletconnectSiws adapter configurationappName
, appDescription
, etc.The backend canister implements several Internet Computer authentication standards:
Provides human-readable consent messages when a user's wallet interacts with the canister. This enhances security by clearly communicating what actions are being requested. Example in src/pnp_skeleton_backend/src/authentication.rs
#[query]
pub fn icrc21_canister_call_consent_message(consent_msg_request: ConsentMessageRequest) -> Result<ConsentInfo, ErrorInfo>
Enables secure delegation of identity, allowing the frontend to perform actions on behalf of the user after authentication. Example in src/pnp_skeleton_backend/src/authentication.rs
#[query]
pub fn icrc_34_get_delegation(request: DelegationRequest) -> Result<DelegationResponse, DelegationError>
#[update]
pub fn icrc_34_delegate(request: DelegationRequest) -> Result<DelegationResponse, DelegationError>
#[update]
pub fn icrc_34_revoke_delegation(request: RevokeDelegationRequest) -> Result<(), DelegationError>
Specifies which origins (domains) are allowed to interact with the canister, enhancing security by preventing unauthorized access. Example in src/pnp_skeleton_backend/src/authentication.rs
#[query]
fn icrc28_trusted_origins() -> Icrc28TrustedOriginsResponse
Start the development server:
npm run dev
Visit the local frontend URL shown in the terminal (typically http://localhost:3000)
The template comes with a ready-to-use authentication system:
/
) has a login button that redirects to /auth
/auth
) shows a SignIn component with wallet optionsConnect to a wallet:
import { auth, connectWallet } from "$lib/stores/auth";
const walletList = auth.getEnabledWallets(); // Get a list of enabled wallets
const selectedWallet = walletList[0]; // Select the first wallet from the list
await connectWallet(selectedWallet.id); // The wallet id field gets passed here.
Check connection status:
import { auth } from "$lib/stores/auth";
$effect(() => {
if ($auth.isConnected) {
// User is authenticated
}
})
Interacting with the backend canister:
import { backendActor } from "$lib/canisters";
// Call a method on the backend
const actor = backendActor({ anon: true })
const result = await actor.greet("World");
Deploy to the IC mainnet:
dfx deploy --network ic