wen-community / wen-program-library

Apache License 2.0
90 stars 20 forks source link

Adds transfer guard program #76

Closed kevinrodriguez-io closed 3 months ago

kevinrodriguez-io commented 4 months ago

Wen Transfer Guard Program

The Wen Transfer Guard Program secures token transfers on the Solana blockchain by enforcing customizable rules using the token_2022 program transfer hook interface.

How It Works

Overview

  1. Create a Transfer Guard Account:

    • Set up an account with a ruleset and identifiable metadata.
  2. Update the Guard:

    • Modify the ruleset of the guard account.
  3. Initialize the Guard:

    • Link the guard to a specific mint.
  4. Execute Transfer Rules:

    • Enforce the ruleset during token transfers.

Instructions Summary

Guard Ruleset Modes

Example Flow (Anchor Based)

Creating a guard

const guardMint = web3.Keypair.generate();

const guardMintAta = getAssociatedTokenAddressSync(
  guardMint.publicKey,
  guardAuthority,
  false,
  TOKEN_2022_PROGRAM_ID
);

const ix = await program.methods
  .createGuard({
    name: "Guard",
    symbol: "GRD",
    uri: "https://example.com/metadata.json",
    additionalFieldsRule: [],
    transferAmountRule: null,
    cpiRule: {
      deny: { 0: [new web3.PublicKey("11111111111111111111111111111111")] },
    },
  })
  .accounts({
    mint: guardMint.publicKey,
    mintTokenAccount: guardMintAta,
    guardAuthority,
    payer,
  })
  .instruction();

const txId = await sendSignedVtx(
  provider,
  payer.publicKey,
  [payer, guardAuthority, guardMint],
  ix
);

Updating a guard

const ix = await program.methods
  .updateGuard({
    additionFieldsRule: [],
    transferAmountRule: null,
    cpiRule: {
      deny: { 0: [] },
    },
  })
  .accounts({
    mint: guardMint.publicKey,
    tokenAccount: guardMintAta,
    guardAuthority: kGuardOwner.publicKey,
  })
  .instruction();

const txId = await sendSignedVtx(provider, payer, [guardAuthority], ix);

Initializing (Assign guard to mint)

const [guardAddress] = web3.PublicKey.findProgramAddressSync(
  [
    Buffer.from("wen_token_transfer_guard"),
    Buffer.from("guard_v1"),
    // Not to be confused with the actual mint for the guard to be assigned to.
    guardMint.publicKey.toBuffer(),
  ],
  program.programId
);
const extraMetasAddress = getExtraAccountMetaAddress(
  mint.publicKey,
  program.programId
);

const ix = await program.methods
  .initialize()
  .accountsStrict({
    guard: guardAddress,
    mint: mint.publicKey,
    mintAuthority: mintAuthority.publicKey,
    payer: payer.publicKey,
    extraMetasAccount: extraMetasAddress,
    systemProgram: web3.SystemProgram.programId,
  })
  .instruction();

await sendSignedVtx(provider, payer.publicKey, [payer, mintAuthority], ix);

Executing a transfer

let ix = await createTransferCheckedWithTransferHookInstruction(
  provider.connection,
  sourceAta,
  mint.publicKey,
  destinationAta,
  sourceAuthority.publicKey,
  BigInt(1e8), // Amount, 1e8 would be something like 0.1 sol (If using 9 decimals).
  mint.decimals,
  undefined,
  undefined,
  TOKEN_2022_PROGRAM_ID
);

await sendSignedVtx(
  provider,
  context.payer.publicKey,
  [kSourceAuthority, context.payer],
  ix
);
balmy-gazebo commented 4 months ago

How are you planning to implement the deny/allow list structure here? I don't see any struct for it, will it be a PDA point to a re-sizable list of other IDs?

kespinola commented 4 months ago

Here is a first take on guard structure to support cpi, amount, and additional field rules. If a guard has multiple rules set then all must pass for the transfer to be allowed.

If the rule is none or vec is empty then the rule is skipped.

// Control which protocols can interact with a mint's tokens. eg only allow royalty respecting protocols to facilitate transfers.

enum CPIRule {
  Allow(Vec<Pubkey>)
  Deny(Vec<Pubkey>)
}

// Enforce the transfer amount is above, below, equal to, or within a range set my the mint authority.
enum TransferAmountRule {
  Above(u64),
  Below(u64),
  Equal(u64),
  Rang(u64, u64)
}

enum MetadataAdditionalFieldRestriction {
  Includes(Vec<String>),
  Excludes(Vec<String>)
}

// Ensure a field exists and if desired is equal to some value. If multiple rules are set then all must pass.
struct MetadataAdditionalFieldRule {
  field: String,
  value_restrictions: Option<MetadataAdditionalFieldRestriction>
}

seed: ["wen_token_guard", "guard_v1", {mint}]
struct GuardV1 {
  cpi_rule: Option<CPIRule>,
  transfer_amount_rule: Option<TransferAmountRule>,
  addition_fields_rule: Vec<AdditionalFieldRule>
}

/// Ensure the token has 
enum Guard {
  V1(GuardV1)
}