見出し画像

EIP-681とEIP-3009を使った、eコマースにおけるJPYC決済プラットフォーム

※これはXに書いた記事の丸写しです。

著:TANAKA SENKI

それぞれの規格について


EIP-681

transferトランザクションの可読化を目指した規格。これは、

ethereum:${contractaddress}/transfer?address=${address}&uint256=${amount * decimal}

というように書ける。

これを相手に送付して、トランザクションを作ってもらい、署名すれば、トランザクションを行える。ただし、javascriptの場合、uint256のところは、桁数問題からbigintで値渡しをするのが推奨される。

EIP-3009

誰がガス代を支払うかといったトランザクションの変形。一般的にEVMの仕様では、アナウンス者がガス代の支払いを行う。これを用いると、

ユーザーの署名 -> アナウンス -> アナウンスした人がガス代を払い、トランザクションが実行される

というようなこともできる。

一般的に、これらの情報が追加で必要になる。

  • valiedAfter 署名が有効になる開始時刻(基本的に0)

  • valiedBefore 署名が有効な終了時刻(有効期限)(10分くらいあればいい)

  • nonce 固有のランダムな 32 バイト識別子

  • v (uint8), r (bytes32), s (bytes32) 署名の構成要素


これらの組み合わせで決済を行う

これを行うに際し、プラットホーム内で署名をしなければならず、そのためには、ウォレットをプラットホームと接続する必要がある。これに関しては、PCとwebの両方で使えるようにするなら reownというサービスを使うのがよい。(コードは長いので割愛)。

まず、料金とアドレスが決定したら、サーバー経由でEIP-681規格のURIを送る。その際のカスタムURIはこんな感じでいいだろう。

ethereum:${contractaddress}/transfer?address=${address}&uint256=${amount * decimal}&nonce=${nonce}

nonceは例えば注文番号を用いると、後で共有しやすい。

その後nonceは
nonce = keccak256("ORDER-20260722-001");
というように変換する。

その後、店頭のプログラムで、以下のように支払者のウォレットで署名してもらう。

async function signEIP3009Raw(
  jpycContractAddress: string,
  chainId: number,
  fromAddress: string,
  toAddress: string,
  amountWei: string,
  nonce: string // bytes32 hex
) {
  const validBefore = Math.floor(Date.now() / 1000) + 900; //ここでは15分とする

  const msgParams = JSON.stringify({
    domain: {
      name: "JPYC",
      version: "2",
      chainId: chainId,
      verifyingContract: jpycContractAddress,
    },
    message: {
      from: fromAddress,
      to: toAddress,
      value: amountWei,
      validAfter: 0,
      validBefore: validBefore,
      nonce: nonce,
    },
    primaryType: "TransferWithAuthorization",
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
      ],
      TransferWithAuthorization: [
        { name: "from", type: "address" },
        { name: "to", type: "address" },
        { name: "value", type: "uint256" },
        { name: "validAfter", type: "uint256" },
        { name: "validBefore", type: "uint256" },
        { name: "nonce", type: "bytes32" },
      ],
    },
  });

  // RPCリクエストを直接発行
  const signature: string = await window.ethereum.request({
    method: "eth_signTypedData_v4",
    params: [fromAddress, msgParams], // 第1引数: 署名者のアドレス, 第2引数: JSON文字列
  });

  // 65バイトのHex文字列(0x...)から v, r, s をスライスして取り出す
  const r = "0x" + signature.substring(2, 66);
  const s = "0x" + signature.substring(66, 130);
  const v = parseInt(signature.substring(130, 132), 16);

  return { v, r, s };
}

その後出来上がったものを、サーバーに返す。これはこんな感じでカスタムURIを使っていいだろう。(nonceやvaliedAfterやvaliedBefore、to、from、Amountはサーバー側で共有しておくこと)

ethereum3009:orders/${注文番号}?v=${v}&r=${r}&s=${s}

サーバーはこの注文番号を照合し、トランザクションを送る。

async function sendJpycPayment(contractAddress, from, to, valueWei, validAfter, validBefore, nonce, v, r, s) {
  // 1. スマートコントラクトの関数を実行するための「Data (calldata)」を作成する
  // transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)
  // の関数シグネチャハッシュ + パラメータエンコード値
  const functionInterface = new ethers.Interface([
    "function transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)"
  ]);
  
  const calldata = functionInterface.encodeFunctionData("transferWithAuthorization", [
    from,
    to,
    valueWei,
    validAfter,
    validBefore,
    nonce,
    v,
    r,
    s
  ]);

  // 2. 店舗側ウォレットから eth_sendTransaction を発行
  try {
    const txHash = await window.ethereum.request({
      method: "eth_sendTransaction",
      params: [{
        from: shopRelayerAddress, // 店舗側のウォレット(ガス代を払う人)
        to: contractAddress,      // JPYCのコントラクトアドレス
        data: calldata,           // 作成したエンコードデータ
      }],
    });

    console.log("送信成功! Tx Hash:", txHash);
    return txHash;

  } catch (error) {
    console.error("送信失敗:", error);
  }
}

これで完成だ。途中でTypescriptだったりJavascriptだったりプログラム言語が変わっているのは、許してね


これでeコマースはどう変わる?

プラットフォーム負担になるが、ガスレス決済ができる。

振込手数料やクレジットカードの手数料は、結構無視できない額になっている。ブロックチェーン決済を通して、お得なeコマースを行おう。


あとがき

これを作成するにあたって、コード作成に協力いただいた、Google geminiに感謝の意を表明する。

必ずしも正しいかと言えば、テストは未実施なので、検証は各自でお願いします。

以下、コーヒー代

ここから先は

80字

¥ 330

Amazon Payで支払うと最大2%還元のチャンス! 9/30まで

この記事が気に入ったらチップで応援してみませんか?