Securely delegate your Solana assets without compromising your cold wallet's security
Proof of ownership is one of the more critical yet highly neglected problems 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.
Custos Protocol aims to remedy this and much more! 🚀
Custos provides a seamless and secure way to delegate assets from cold to warm wallets. This allows users to prove ownership of their assets without risking them by connecting to insecure websites or moving them around.
Custos Javascript SDK that can be used readily by developers to interact with the Custos Protocol.
The SDK can be used by:
- Wallets to allow users to delegate a cold wallet and its assets to a hot wallet
- Dapps to fetch the all delegates for a given hot wallet
NPM Package: https://www.npmjs.com/package/custos-js-sdk
Install SDK:
// Yarn
yarn add custos-js-sdk
// npm
npm install custos-js-sdk
For more info, checkout the guide here: https://github.com/custosprotocol/custos-js-sdk
Users can choose what assets they want to delegate to their hot wallet of choice. These assets can be anything: NFTs or liquid fungibles. Users can also choose the number of tokens they would like to delegate.
Custos Protocol is a fully on-chain Solana program and does not involve storing data off-chain and performing backend operations on unverified and insecure centralized cloud servers.
There are two main actions that can be performed on Custos Protocol:
- Delegation
- Revoking delegation
For delegating assets, we provide users three options to allow users to be in full control of what gets delegated:
- Full Wallet Delegation: The entire wallet gets delegated, including all assets that are owned by the wallet.
- NFT Delegation: Single or multiple NFTs can be selected to be delegated to one or more wallets.
- Liquid Token Delegation: Liquid Tokens or fungible SPL-tokens can be delegated to multiple wallets.
The following types of wallets can be used to be delegated using Custos Protocol:
- Hardware Wallets [recommended]
- Hot Wallets
- Multisig Wallets [soon]
If an asset in your cold wallet is delegated to your hot wallet:
- The hot wallet is simply “shadowing” as the owner of the asset
- The hot wallet does not own any write / transfer rights over this asset, not has the ability to transfer these rights to anyone else.
- The cold wallet remains the owner of this asset on-chain. With our delegation standard however, the hot wallet can be proven to be a shadow-owner of this asset, and hence the hot wallet owner can prove ownership of that given asset.
We are using PDAs to to derive delegation accounts for full wallet or token delegation.
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(),
]
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(),
]
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>,
}
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;
};
- Claiming Airdrops
- Claiming Allowlists
- Token-gated mints
- Token-gated games
- DAO Governance
- On-chain reputation
- Verifiable Wallet aggregation
- 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.