Wallet Delegation Standard
Summary
This RFC introduces a delegation standard to mitigate loss, theft, and unauthorized access to funds stored in cold wallets. The standard maintains the offline nature of cold wallets, allowing them to delegate ownership of assets to dedicated hot wallets, thus allowing secure and controlled proof of ownership of assets.
Motivation: Proof of Ownership
Proof of ownership is a critical yet highly neglected problem in the Solana ecosystem. In its current form, it’s highly insecure
- users must connect their cold wallets to unverified websites, or
- transfer their valuable assets to warm wallets
Goals
This standard aims to remedy this by providing a secure way to delegate assets from cold to hot wallets. This allows users to prove ownership of their assets without risking them by connecting to insecure websites or moving them around.
Background
Delegation
Delegating an asset from a cold wallet → hot wallet: The hot wallet is shadowing as the owner of the asset, and the hot wallet does NOT own any write/transfer rights over this asset, nor does it have the ability to transfer these rights to anyone else. The cold wallet remains the true owner of this asset on-chain.
Actions
Two main actions can be performed using the proposed protocol:
- Delegation
- Revoking Delegation
Delegation Types
- Full Wallet Delegation: The entire wallet gets delegated, including all assets that are owned by the wallet.
- Token Delegation: Tokens having the same mint address can be delegated. This includes fungibles, semi-fungibles, and non-fungibles.
Proposed Implementation
PDAs can be used to derive delegation accounts for full wallet or individual token Delegation.
DelegationAccount
To delegate wallet A to another wallet B, a DelegationAccount
can be derived using the address of wallet A.
#[account]
pub struct DelegateAccount {
// Cold Wallet
pub authority: Pubkey,
pub hot_wallet: Pubkey,
}
where seeds for DelegateAccount:
[
DelegateAccount::PREFIX.as_bytes(),
authority.key().as_ref(),
]
TokenDelegationAccount
In the case of token Delegation, i.e., delegating a token in wallet A with ATA (associated token account) T to wallet B, a TokenDelegationAccount
can be derived using the address of wallet A and the ATA T.
#[account]
pub struct DelegateTokenAccount {
pub authority: Pubkey,
pub hot_wallet: Pubkey,
pub token_account: Pubkey,
}
where seeds for DelegateTokenAccount:
[
DelegateTokenAccount::PREFIX.as_bytes(),
authority.key().as_ref(),
token_account.key().as_ref(),
]
Revoking Delegations
The authority of the Delegation can revoke delegations by closing the PDA.
#[derive(Accounts)]
pub struct RevokeDelegate<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut, has_one=authority, close=authority)]
pub delegate_account: Account<'info, DelegateAccount>,
}
#[derive(Accounts)]
pub struct RevokeTokenDelegate<'info> {
pub authority: Signer<'info>,
#[account(mut, has_one=authority, close=authority)]
pub delegate_token_account: Account<'info, DelegateTokenAccount>,
}
Fetching Delegations
This standard would require an SDK for client-side fetching delegated wallets. When a user connects their wallet, the SDK can fetch all the assets delegated to the given (connected) hot wallet. Here is a reference implementation of the SDK:
getUserDelegates = async (hotWallet: PublicKey) => {
const data = await this.delegationProgram.account.DelegateAccount.all([
{
memcmp: {
offset: 8 + 32,
bytes: hotWallet.toBase58(),
},
},
]);
return data;
};
getTokenDelegates = async (hotWallet: PublicKey) => {
const data = await this.delegationProgram.account.DelegateAccount.all([
{
memcmp: {
offset: 8 + 32,
bytes: hotWallet.toBase58(),
},
},
]);
return data;
};
Potential Use-Cases
- Claiming Airdrops
- Claiming Allowlists
- Token-gated mints
- Token-gated games
- DAO Governance
- On-chain reputation
- Verifiable Wallet aggregation
Ongoing Investigation
- Supporting Programmable Wallets: Smart contract wallets like Multisigs are not system accounts; hence, the current implementation for the standard can be changed to support PDAs that represent programmable wallets.
- Allowing tightly scoped rights to the delegated hot wallet for making transactions on behalf of the cold wallet.
- Delegation Expiration: Current implementation requires users to manually revoke a delegation if they no longer want a given hot wallet to be the Delegation for a cold wallet. Automatic expiration could be introduced to let users set a dedicated interval, after which the
Delegation/TokenDelegationAccount
will be closed.
Call For Action
- Readers: Requesting readers to provide feedback, audit reference implementation, and contribute to the codebase.
- Dapps: Requesting dapps to explore the SDK reference implementation and provide feedback for making integration of this standard easy.
- Wallets: Requesting wallets to explore the SDK reference implementation to provide users an easy way to perform the Delegation inside the wallets themselves - connecting cold wallets to any website (even one that is created specifically for this standard) goes against the very core principle behind this standard.
Reference Implementation
- Delegation Program: GitHub - custosprotocol/custos: Custos Protocol
- JS SDK: GitHub - custosprotocol/custos-js-sdk: JavaScript SDK for Custos Protocol
Citation
Anvit Mangal <@0xprof_lupin>, Pratik Saria <@PratikSaria>. “sRFC 00012: Wallet Delegation Standard for Secure Proof of Ownership,” https://forum.solana.com/t/srfc-00012-wallet-delegation-standard-for-secure-proof-of-ownership, May 2023.