Jacky Gu

CPT-255技术文档

15 Nov 2023 Share to

简述:

CPT255让任何推特用户获得方便地发行NFT的能力,并将此NFT与多种社交软件绑定,实现Web3社交。

功能和关键特性描述: 推特用户通过其账号发行一款推特头像图片的KeyNFT,将KeyNFT用在TG/Chatpuppy等社交软件中;每款KeyNFT总量255枚;每枚KeyNFT的价格为$(id-1)^\frac{5}{4}/k$ ETH;买入时免费;卖出时按VAT方式计费并支付给KeyNFT的发行者,协议和推荐人;KeyNFT满255个时拆分成总量约1万的可自由交易的TickNFTKeyNFT持有者免费获得不等的TickNFT

特点:

  • 总量有限。每款KeyNFT总量255枚,每款TickNFT最多10703枚。
  • 价格平滑。采用$\frac{5}{4}$次幂,让价格更平滑,降低早期和晚期参与者之间成本差。
  • 采用VAT(Value-added Tax)收费,收费更公平。
  • 先内部交易后自由交易。内部交易的价格由智能合约确定,全部255枚KeyNFT铸造完成后,拆分并可自由交易。这种分阶段方案让NFT的冷启动更有保障。
  • 资金池用于生态发展。在拆分后,销售KeyNFT的资金可用来推动运营,其中40%用于特别奖励。
  • 使用AA钱包(account abstration wallet),用户使用门槛低,更安全,有效防刷单,更加公平。

本文就CPT-201协议的几个比较重要且复杂的技术问题解答如下。

1- KeyNFT的价格

1.1- KeyNFT的买入价格:

\(P_b=\frac{(id - 1)^\frac{5}{4}}{k}\) \((id > 0)\)

其中idKeyNFT的序号,用户购买一个KeyNFTid会加一,用户卖出一个KeyNFTid会减一。

1.2- KeyNFT的买出价格为:

\(P_s=\frac{(suply - 1)^\frac{5}{4}}{k}\) \((supply > 1)\)

supply为当前KeyNFT的总量,因为不允许卖出最后一个KeyNFT,所以卖出时的公式中supply>1

1.3- 价外税(费)

所有费用和VAT都是价外税(费),即:

  • 购买时,用户支付金额 = $P_b + BuyFee$,BuyFee暂定为2%,所以实际支付金额 = $1.02*P_b$
  • 买出时,需要在上述$P_s$基础上扣除VAT后,再给到卖出方,即 $实际所得 = P_s - (P_s - P_b) * VATRate$,VATRate暂定为30%,所以: $实际所得 = P_s - (P_s - P_b)*0.7$(VAT详细内容见下文)

1.4- 销售总额

  • 当所有KeyNFT被销毁时,协议内用于回购的资金池归为0。

  • 当所有KeyNFT被铸造完,协议内用于回购的总资金为:

\(\sum\frac{(id - 1)^\frac{5}{4}}{k}\) \((id>0)\)

1.5- 关于系数$k$和NFT最大发行量

经过测算,协议设置:

  • $k=5000$
  • 每款KeyNFT的最大数量为255枚(这也是本协议命名为CPT255的原因)

在这两个参数作用下:

  • 初始化第1个NFT时,价格为$\frac{(1-1)^\frac{5}{4}}{5000}$ = 0;
  • 在购买第2个NFT时,价格为$\frac{(2-1)^\frac{5}{4}}{5000}$ = 0.0002 ETH;
  • 在购买第50个NFT时,价格为$\frac{(50-1)^\frac{5}{4}}{5000}$ = 0.025928 ETH;
  • 在购买第100个NFT时,价格为$\frac{(100-1)^\frac{5}{4}}{5000}$ = 0.062456 ETH;
  • 在购买第200个NFT时,价格为$\frac{(200-1)^\frac{5}{4}}{5000}$ = 0.149485 ETH;
  • 在购买第255个NFT时,价格为$\frac{(200-1)^\frac{5}{4}}{5000}$ = 0.202802 ETH;

1.6- 价格曲线:

1.7- 价格变化率曲线:

价格涨幅呈现一开始很高(如在购买第3个KeyNFT时的价格是0.000476ETH,比第2个高138%;之后涨幅降低,从第65个KeyNFT开始,单个涨幅小于2%;从第128个KeyNFT开始,单个涨幅小于1%,第255个KeyNFT的价格比第254个高0.5%。

价格变化%公式:

\[\delta = (\frac{id-1}{id - 2})^\frac{5}{4} - 1\] \[id > 1\]

图表如下:

2- 费用和VAT(Value-Added Tax)

2.1 购买费用

在购买KeyNFT时,会在上述计算得出的价格基础上增加2%的费用,该费用会立即打到用户的推荐人账户。

2.2 VAT的计算

在卖出KeyNFT时,会依据买入时的价格和卖出价格之间的利润收取VAT,如果亏损,即买入价格>=卖出价格时,则不收取任何VAT。

VAT计算公式: \(VAT=(P_s - P_b)*R\) R暂定为30%

举例:

Alice买入第10个NFT,价格为$(10-1)^\frac{5}{4}/5000$=0.003118 ETH,然后又买入第16个NFT,价格为$(16-1)^\frac{5}{4}/5000$=0.005904 ETH。

当第40个NFT被购买,即价格为$(40-1)^\frac{5}{4}/5000$=0.019492 ETH 时,Alice决定卖出其中一个NFT(合约会自动卖出ID最大的NFT,因为这个成本最高,可以降低VAT),因此,卖出的这个NFT的id为16,这个NFT的毛利润为0.019492 - 0.005904 = 0.013588 ETH,她将支付0.013588 * 30% = 0.0040764 ETH作为VAT,实际获得0.019492 - 0.0040764 = 0.0154156 ETH

采取VAT方式,比采取根据交易金额收取费用更公平。但是在很多场景下,因为无法确定购买成本,很难采用VAT方式,下文2.6节将介绍本协议获取购买成本的Tagged NFT解决方案。

2.3- VAT率与费用率之间关系

来比较一下VAT和常规的根据交易金额收取费用的区别:

假设毛利润率为$P$,VAT率为$R_v$,依据销售额征收的费用率为$R_f$,则

\[\frac{R_f}{R_v} = \frac{P}{P+1}\]

如果要让VAT等于依据销售额征收的费用,则:$(P+1)R_f = PR_v$,得到:

\[P = \frac{R_f}{R_v-R_f}\]

如果,$R_v$ = 30%,$R_f$ = 10%,则$P = 50%$,即如果毛利润为50%的话,征收30%的VAT,相当于依据销售额征收10%的费用。当毛利润率高于50%时,VAT会高于10%的费用,但当毛利润低于50%时,VAT会低于10%的费用。

在这种情况下,如果用户购买后后悔,想立即卖出,则无需支付VAT,因为买卖价格一致。

2.4- 实际利润率

在征收VAT后,实际利润率$P’$为: \(P' = P*(1 - R_v)\)

如用户在卖出时的税前毛利润率为200%,则其实际利润率为200%*(1-30%)=140%

2.5- VAT的分配

VAT分配暂定如下:

  • NFT发行者,获得税前毛利润的15%
  • 推荐人,获得睡前毛利润的9%
  • 协议,获得税前毛利润的6%

2.6- 记录每个NFT的购买价格 - Tagged ERC1155

为了实现上述在出售时,精确的计算毛利润以及VAT,需要知道每个KeyNFT的购买成本。根据前几节介绍,我们知道购买成本取决于KeyNFT的 ID,所以这个问题变成了如何知道所拥有的每个KeyNFT的ID。

ERC1155标准把不同NFT放在了一个合约里,每个NFT拥有从1开始的ID,但是ERC1155并没有实现如何确定某个地址拥有哪些ID,或者某个ID归属于哪个地址。

为了解决这个问题,我们在ERC1155基础上,扩展了名为Tagged ERC1155的新方案。

因为限定了每个NFT最多不超过255个,所以,可以使用一个uint256类型的数值来确定用户拥有的NFT的ID。

$\underbrace{a_{255},a_{254},a_{253},a_{252},a_{251},a_{250},a_{249},a_{248},a_{247},\cdots,a_{6},a_{5},a_{4},a_{3},a_{2},a_1}_{255 bits}$

当$a_n=1$时,说明拥有该位置的KeyNFT,当$a_n=0$时,不拥有。

2.6.1 数据结构

在合约里,我定义了如下数据结构,用来保存拥有的NFT的ID:

struct ID {
    uint256 a; // 代表#1-#255 KeyNFT
}
mapping(uint32 => address => ID) nftIds 

如果NFT数量比较多,可以进一步扩展结构中的参数。

2.6.2 数据解析

例如,当nftIds.a = 5,即二进制数0b0101时,说吗该用户拥有第#1,#3号NFT。 再例如,当nftIds.a = 4718592,即二进制数0b10010000000000000000000时,说明该用户拥有第#20,#23号NFT。

来个复杂点的,当nftIds为{a: 104018, b: 846747115870232577},即a=0b11001011001010010,b=101111000000010000000000010000000000000010000010000000000001,根据a值,说明该用户拥有第#2,#5,#7,#10,#11,#13,#16,#17号NFT,根据b值,说明该用户还拥有第#257,#270,#276,#291,#303,#311,#312,#313,#314,#316号NFT,合在一起,即是该用户所拥有的NFT,一共有18个。

以下是用于解析两个unit256数值的solidity智能合约代码:

    function parse() public view returns(uint16[] memory pos, uint16 sum) {
        ID memory id = nftIds[msg.sender];
        pos = new uint16[](totalSupply());
        uint16 i = 1;
        for (i = 1; i <= 256; i++) {
            if(id.a == 0) break;
            if(id.a % 2 == 1) pos[sum++] = i; 
            id.a = id.a / 2;
        }
    }

该方法返回两个参数,第一个是位置的数组,第二个是拥有nft的总量。

2.6.3 防止ID重复

为了防止重复买入相同ID的NFT,需要设置一个mapping(uint16 id => address) nftAddresses;,用来记录某个KeyNFT的所有者,当该值为0x0时,说明不被任何人拥有。

通过以下exist合约方法,判断是否已经被拥有。

    function exist(uint16 id) public view returns(bool) {
        return nftAddresses[id] != address(0x0);
    }

2.6.4 买入NFT时

当购买一个KeyNFT时,用以下add方法用来添加买入的KeyNFT的ID:

    function add(uint16 id) public {
        nftIds[msg.sender].a += 1 << (id - 1);
        nftAddresses[id] = msg.sender;
    }

上述代码中,使用1<<n取代2^n,以提高计算效率,下同。

2.6.5 卖出NFT时

当卖出一个NFT时,用以下remove方法用来删除卖出的KeyNFT的ID:

    function remove(uint16 id) public {
        nftIds[msg.sender].a -= 1 << (id - 1);
        nftAddresses[id] = address(0x0);
    }

3- KeyNFT的拆分

3.1 KeyNFT的转让

KeyNFT不可互相转让(在智能合约中,将transfertransferFrom等方法的权限关闭),只能通过本智能合约按照协议规则买(即mint)和卖(即burn)。在KeyNFT阶段,KeyNFT可以在钱包中查看,可以上架OpenSea等支持ERC1155的NFT交易所,但是无法转让和交易。

KeyNFT的发行人可随时将转让状态从“不可转让”调整为“可转让”,但是一旦设为“可转让”,无法再设回“不可转让”,可转让的NFT即TickNFT

KeyNFT变为“可转让”的TickNFT后,原KeyNFT持有人可调用ReMint合约方法,将手上的KeyNFT拆分。

拆分规则和拆分后的TickNFT总量,见下文。

3.2 拆分时TickNFT的数量和分配

在所有KeyNFT被铸造出,会进行拆分,并根据KeyNFT的持有以及推荐情况分配拆分后的TickNFT

3.2.1 获得TickNFT方法

所有KeyNFT持有人需要与智能合约进行交互获取TickNFT

3.2.2 获得的TickNFT数量:

  • 如果没有推荐过任何人:一个KeyNFT可获得35个TickNFT
  • 如果推荐n个人入群:则可获得35+5*nTickNFT,即每推荐一个,多获得5枚。

3.2.3 TickNFT总量

TickNFT总量计算公式:

\[T = K*B + (K - m - n)*C\]

其中:

  • T:TickNFT总量
  • B:每个人都能获得的基础TickNFT数量
  • C: 每推荐一个人,奖励的TickNFT数量
  • K:KeyNFT总量,即255
  • m:没有推荐过任何人,且自己不是被群内人推荐的人数
  • n:推荐过其他人入群的人数

上述所有参数皆为正整数,不能出现小数和负数。

从上述公式中可以看出:当C=0,也就是没有推荐奖励时,TickNFT的总量固定。但是我们的情况没有那么简单。

为防止推荐人得到的奖励比没有推荐的人获得的少,这里假设B=i*C,上述公式变为: \(T = (K*i + K - m - n)*C\)

假设:B=35,C=5,i=7,m=50,n=20,则T=255*35+(255-50-20)*5=9850枚

TickNFT数量一旦确定,既无法增发,也无法销毁,只能转让和交易。

3.2.4 TickNFT总量的范围

因为mn两个值是无法固定的,会随着社区的发展而变化,所以TickNFT会存在一个范围。

如果KKeyNFT的持有人中没有任何推荐人,则m=K,n=0,这时TickNFT的总量为:T=K*B= K*i*C

如果KKeyNFT持有人中所有人都是被一个人推荐的,则n=1,m=0,这时TickNFT的总量为:T=K*B+(K-1)*C=(K*i + K - 1)*C

所以,TickNFT的范围为:

\[T \in [K*i*C, (K*i + K - 1)*C]\]

or

\[T \in [K*B, K*B + (K - 1)*C]\]

最大和最小之间相差:(K-1)*C,因为C不可能为0,所以这个差额是永远存在的。

在本案中,C=5,i=7,所以TickNFT的范围为[8925, 10195]

3.3 销售KeyNFT的资金的使用与分配

因为每个NFT的买入价格为

\[P_b=\frac{(id - 1)^\frac{5}{4}}{k}\]

且费用和VAT都是价外税(费),所以:

  • 当所有NFT全部销毁时,协议内用于回购的资金池为0。

  • 255个NFT全部铸造完,协议内收到的销售总额为:

\[\sum\frac{(id - 1)^\frac{5}{4}}{k}\]

因为:id ∈ [1, 255], k=5000,所以全部铸造完成时,合约中将有约23个ETH(22995561951692318400Wei)

该笔资金用途:

  • 8.75ETH,用于特别奖励,见下面;
  • 7.25ETH,KeyNFT发行人;
  • 7ETH,发展基金;

4- 特别奖励

KeyNFT的持有人除了获得TickNFT外,还能参与抽奖,奖金来自于KeyNFT销售金额的38%,即8.75ETH,这笔奖金按以下方案分给3KeyNFT持有人:

  • #1: 5ETH
  • #2: 2.5ETH
  • #3: 1.25ETH

获奖概率根据购买价格加权:

\[r = \frac{P_b}{S}*3\] \[P_b: 购买价格,S: 销售总额,即23ETH\]

举例:

  • 平均中奖概率:3/255=1.18%;
  • 第255个KeyNFT拥有者,获奖概率2.65%;
  • 第204个KeyNFT拥有者,获奖概率2%;
  • 第162个KeyNFT拥有者,获奖概率1.5%;
  • 第134个KeyNFT拥有者,获奖概率1.18%,平均水平;
  • 第118个KeyNFT拥有者,获奖概率1%;
  • 第68个KeyNFT拥有者,获奖概率0.5%;
  • 第33个KeyNFT拥有者,获奖概率0.2%;
  • 第10个KeyNFT拥有者,获奖概率0.04%;

4.1- 抽奖智能合约

为了公平的抽奖,现将合约中的随机数抽奖代码介绍如下:

4.1.1 随机数

随机数种子使用keccak256(abi.encodePacked(block.timestamp, msg.sender, nonce)),即区块链当前时间戳,发送人和nonce。

nonce是一个自增值,所以这个函数是一个改变状态的函数,而不是pureview函数。

    function getRandom() public returns(uint256) {
        uint256 rand = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, nonce))) % 1e8;
        nonce++;
        return rand;
    }

4.1.2 权重Map

bonusProbilityWeight是一个根据$r = \frac{P_b}{S}$,每个ID中奖概率的帕累托累计Mapping。

    mapping(uint16 id => uint256 weight) public bonusProbilityWeight;
    function initialProbilityWeight() internal {
        uint256 weightSum = 0;
        for(uint16 i = 1; i < cap + 1; i++) {
            weightSum += priceTable[i];
            bonusProbilityWeight[i] = weightSum * 1e8 / maxRaised;
        }
    }

4.1.3 获得中奖的ID

    function whoIsLucky() public returns(uint16 i) {
        uint256 rand = getRandom();
        for(i = 1; i < cap + 1; i++) {
            if(rand < bonusProbilityWeight[i]) break;
        }
    }

4.1.4 ID分布模拟

根据以上方法,在模拟了1000次后,ID的分布图如下: 按模拟数据,可知: |ID范围|中奖概率|平均概率| |—|—|–| |1~50|2.4%|0.048%| |51~100|9.6%|0.192%| |101~150|18.6%|0.372%| |151~200|26.2%|0.524%| |201~250|38%|0.76%| |251~255|5.2%|1.04%|

考虑到有三次中奖机会,所以上述概率需要再乘以3。

5- 推荐

5.1 推荐规则

  • 所有发行NFT的用户可称为推荐人,推荐码在Dapp中获取。

  • 被推荐人在买卖KeyNFT前必须绑定推荐人,否则无法买卖。

  • 推荐人关系一旦绑定,无法解除或变更。

  • 被推荐人购买KeyNFT时,推荐次数+1;当被推荐人卖出KeyNFT时,推荐次数-1。

5.2 推荐人的权益

  • 在被推荐人购买KeyNFT时,获得购买金额2%的推荐奖;
  • 在被推荐人出售KeyNFT时,获得VAT的30%,即买卖差价的9%;
  • 在用户提取拆分后的TickNFT时,推荐人获得TickNFT奖励,即:每推荐一个,多获得5枚TickNFT;