> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sequence.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# ガスレス・サーバーレス トランザクション API でコレクティブルをミントする

> Sequence のガスレス・サーバーレス トランザクション API を使ってコレクティブルをミントする方法を解説します。Cloudflare Worker 上で API を実装することで、ユーザーはガス代なしでシームレスに操作できます。

所要時間：20〜30分

Sequence Transactions API はサーバーレスの [Cloudflare](https://cloudflare.com/) Worker 上で実装でき、ゲームやアプリのユーザーは確認サインやガス代の支払いなしでシームレスに利用できます。また、リレイヤーによるトランザクション速度やスループット、再編成を心配する必要がなくなり、Cloudflare による自動スケーリングも利用できます。

以下のステップで、ホスト型ミンター API を 4 ステップで構築する方法をご案内します。

1. [Wrangler Cli で Cloudflare 環境をセットアップしテストをデプロイする](/guides/mint-collectibles-serverless#1-setup-cloudflare-environment-with-wrangler-cli-and-deploy-a-test)
2. [Sequence Builder で ERC1155 コントラクトをデプロイ、スポンサー登録、およびメタデータの更新](/guides/mint-collectibles-serverless#2-deploy-sponsor-and-update-metadata-for-an-erc1155-contract-with-sequence-builder)
3. [EthAuthProof を使って EOA DDoS を防ぐ](/guides/mint-collectibles-serverless#3-use-ethauthproof-to-prevent-eoa-ddos)
4. [コレクティブルをウォレットにミントする](/guides/mint-collectibles-serverless#4-mint-a-collectible-to-wallet)

結果として、以下の仕様を持つセキュアな API が完成します。

* HTTPS GET：blockNumber を返す
* HTTPS POST（proof, address）：コレクティブルをミントし、トランザクションハッシュを返す

<Warning>
  この実装を完了するには、wrangler cli、npm、Sequence Builder の基本知識が必要です。
</Warning>

<Note>
  [全コードはこちらからご覧いただけます](https://github.com/0xsequence-demos/template-cloudflare-worker-sequence-transactions-api)
</Note>

## 1. Wrangler Cli で Cloudflare 環境をセットアップしテストをデプロイする

まず、`mkdir`でディレクトリを作成し、`cd`でそのディレクトリに移動し、`pnpm init`で`package.json`を作成します。

次に、プロジェクトに wrangler cli がインストールされていることを確認し、`wrangler` キーワードをローカル bash セッションのエイリアスとして設定します。

```shell theme={null}
pnpm install wrangler --save-dev
alias wrangler='./node_modules/.bin/wrangler'
```

[Cloudflareサイト](https://cloudflare.com/)でアカウントを作成し、ログインしてCloudflareダッシュボードにアクセスし、Cloudflareプラットフォームをローカル開発環境と接続します。

```shell theme={null}
wrangler login
```

ログイン後、ディレクトリ内でプロジェクトを初期化し、気に入ったランダム生成のプロジェクトフォルダ名を選択して、git で管理されている TypeScript の `"Hello World" Worker` アプリケーションを初期化するプロンプトに従ってください。

```shell theme={null}
wrangler init
```

このステップを完了するには、`wrangler init`後にエンターを4回押し、最後の2つの質問には`No`と答えてgitバージョン管理とデプロイをスキップしてください。

これにより、クラウドにコードをデプロイできるスターターリポジトリがクローンされます。

<Note>
  ローカル API テスト <br />
  ガイドの任意のタイミングで、プロジェクトフォルダ内で `wrangler dev` コマンドを使ってローカルテストを実行できます。
</Note>

#### デプロイテスト

最後に、ランダムに生成されたプロジェクトフォルダに`cd`で移動し、`wrangler deploy`コマンドを実行します。

これによりURLが表示されるので、ブラウザで`https://<app>.<account>.workers.dev`にアクセスし、「Hello World!」の結果を確認できます。

## 2. Sequence Builder で ERC1155 コントラクトをデプロイ、スポンサー登録、およびメタデータの更新

<Warning>
  Transactions API を利用するには、Sequence Builder でプロジェクトの課金プランを `Developer` にアップグレードする必要があります。[こちらの手順](/solutions/builder/project-settings#5-billing-settings) をご参照ください。
</Warning>

まずは[このガイド](/solutions/builder/contracts/deploy-an-item-collection)に従ってコントラクトをデプロイしてください。

次に、Sequence Builder でコントラクトのロールアクセスを2つの手順でミンターウォレットアドレスからのみリクエストを受け付けるように設定します。

Sequence Builderで `minter permission` を `Sequence Wallet Transactions API Address` に付与してください。

利用する transactions API アドレスを知るには、まず以下のいずれかの方法で取得します。

1. [このアプリ](https://sequence-ethauthproof-viewer.vercel.app/) を使い、ネットワークを選択して `generate local wallet` ボタンでウォレットキーを生成することで取得できます（デモ目的でのみご利用ください）。
2. `推奨`: 以下のコードスニペットを使い、EOA ウォレットの秘密鍵から生成されたアカウントアドレスをローカルで出力することもできます。

```ts theme={null}
import { Session } from "@0xsequence/auth";
import { ethers } from "ethers";

(async () => {
  // Generate a new EOA
  // const wallet = ethers.Wallet.createRandom()
  // const privateKey = wallet.privateKey

  // Or, use an existing EOA private key
  const privateKey = "";

  // Open a Sequence session, this will find or create
  // a Sequence wallet controlled by your server EOA
  const session = await Session.singleSigner({
    signer: privateKey,
    projectAccessKey: "access_key",
  });

  const signer = session.account.getSigner(1);
  console.log(`Your transactions API wallet address: ${signer.account.address}`);
})();
```

プロジェクトを開き、`Contracts` ページに移動し、`Linked contracts` を選択、`Write Contract` タブで `grantRole` メソッドを展開します。

以下の情報で入力してください。

`bytes32 role`: `0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6`

`address account`: `<Generated Sequence Transactions API Wallet Address>`

<Frame>
  <img src="https://mintcdn.com/sequence-0fb8d9e6/Kjh3D5BsUs2mHn5e/images/relayer/grant_role.png?fit=max&auto=format&n=Kjh3D5BsUs2mHn5e&q=85&s=d76fd1a003cf7c8be45dcedad05664f3" alt="リレイヤーにロールを付与する" width="1303" height="575" data-path="images/relayer/grant_role.png" />
</Frame>

ここで入力する `role` 文字列は、solidity では `keccak256("MINTER_ROLE")`、javascript では `ethers.keccak256(ethers.toUtf8Bytes("MINTER_ROLE"))` の結果です。

これにより、特定のアドレスのみがコントラクトからミントできるようになり、それ以外はエラーとなります。

`write` をクリックし、スポンサー付きトランザクションに署名してロールの更新を完了してください。

#### メタデータの更新

次に、コントラクトのメディアやアセットなどのメタデータを更新する必要があります。[このガイド](/solutions/builder/collections/manage-item-metadata)に従って設定できます。

#### コントラクトのスポンサー登録

最後に、コントラクトをスポンサーするには[このガイド](/solutions/builder/gas-sponsorship)を参考にしてください。

## 3. EthAuthProof を使って EOA DDoS を防ぐ

コントラクトのデプロイが完了したら、Cloudflare Worker のディレクトリとプロジェクトに戻り、`ethers` と `0xsequence` をインストールして Sequence API にアクセスし、リクエストが信頼できる Sequence ウォレットから来ているかどうかの証明を検証します。

```shell theme={null}
pnpm install 0xsequence @0xsequence/network
```

次に、POST か GET かを判定後、ミドルウェアを追加してください。POST リクエストの場合は、渡された `proofString` と `address`、および環境変数が有効かどうかを検証します。

`src/index.ts` に配置するコードの雛形はこのようになります。`callContract` と `getBlockNumber` はモック化されており、コントラクト呼び出し前に `verify` を呼ぶ検証ステップが含まれています。

```ts theme={null}
import { sequence } from "0xsequence";
import { networks, findSupportedNetwork } from "@0xsequence/network";

export interface Env {
  PKEY: string; // Private key for EOA wallet
  CONTRACT_ADDRESS: string; // Deployed ERC1155 or ERC721 contract address
  PROJECT_ACCESS_KEY: string; // From sequence.build
  CHAIN_HANDLE: string; // Standardized chain name – See https://docs.sequence.xyz/multi-chain-support
}

// use the sequence api to verify proof came from a sequence wallet
const verify = async (
  chainId: string,
  walletAddress: string,
  ethAuthProofString: string
): Promise<Boolean> => {
  const api = new sequence.api.SequenceAPIClient("https://api.sequence.app");
  const { isValid } = await api.isValidETHAuthProof({
    chainId,
    walletAddress,
    ethAuthProofString,
  });
  return isValid;
};

async function handleRequest(
  request: Request,
  env: Env,
  ctx: ExecutionContext
): Promise<Response> {

  if (request.method === "OPTIONS") {
		return new Response(null, {
			headers: {
				// Allow requests from any origin - adjust this as necessary
				"Access-Control-Allow-Origin": "*",
				
				// Allows the headers Content-Type, your-custom-header
				"Access-Control-Allow-Headers": "Content-Type, your-custom-header",
				
				// Allow POST method - add any other methods you need to support
				"Access-Control-Allow-Methods": "POST",
				
				// Optional: allow credentials
				"Access-Control-Allow-Credentials": "true",
				
				// Preflight cache period
				"Access-Control-Max-Age": "86400", // 24 hours
			}
		});
	}

  if (env.PKEY === undefined || env.PKEY === "") {
    return new Response("Make sure PKEY is configured in your environment", {
      status: 400,
    });
  }

  if (env.CONTRACT_ADDRESS === undefined || env.CONTRACT_ADDRESS === "") {
    return new Response(
      "Make sure CONTRACT_ADDRESS is configured in your environment",
      { status: 400 }
    );
  }

  if (env.PROJECT_ACCESS_KEY === undefined || env.PROJECT_ACCESS_KEY === "") {
    return new Response(
      "Make sure PROJECT_ACCESS_KEY is configured in your environment",
      { status: 400 }
    );
  }

  if (env.CHAIN_HANDLE === undefined || env.CHAIN_HANDLE === "") {
    return new Response(
      "Make sure CHAIN_HANDLE is configured in your environment",
      { status: 400 }
    );
  }

  const chainConfig = findSupportedNetwork(env.CHAIN_HANDLE);

  if (chainConfig === undefined) {
    return new Response("Unsupported network or unknown CHAIN_HANDLE", {
      status: 400,
    });
  }

  // POST request
  if (request.method === "POST") {
    // parse the request body as JSON
    const body = await request.json();
    const { proof, address, tokenId }: any = body;
    try {
      // check that the proof is valid
      if (await verify(env.CHAIN_HANDLE, address, proof)) {
        try {
          // mocked call
          const res = await callContract(request, env, address, tokenId);
          return new Response(`${res.hash}`, { status: 200 });
        } catch (err: any) {
          console.log(err);
          return new Response(`Something went wrong: ${JSON.stringify(err)}`, {
            status: 400,
          });
        }
      } else {
        return new Response(`Unauthorized`, { status: 401 });
      }
    } catch (err: any) {
      return new Response(`Unauthorized ${JSON.stringify(err)}`, {
        status: 401,
      });
    }
  }
  // GET request
  else {
    try {
      // mocked call
      const res = await getBlockNumber(env.CHAIN_HANDLE, request);
      return new Response(`Block Number: ${res}`);
    } catch (err: any) {
      return new Response(`Something went wrong: ${JSON.stringify(err)}`, {
        status: 500,
      });
    }
  }
}

const getBlockNumber = async (
  chainId: string,
  request: Request
): Promise<number> => {
  return chainId;
};

const callContract = async (
  request: Request,
  env: Env,
  address: string,
  tokenId: number
): Promise<ethers.providers.TransactionResponse> => {
  return { hash: "0x" } as any;
};

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    // Process the request and create a response
    const response = await handleRequest(request, env, ctx);

    // Set CORS headers
    response.headers.set("Access-Control-Allow-Origin", "*");
    response.headers.set(
      "Access-Control-Allow-Methods",
      "GET, POST, PUT, DELETE, OPTIONS"
    );
    response.headers.set("Access-Control-Allow-Headers", "Content-Type");

    // return response
    return response;
  },
};
```

#### Cloudflare 環境変数の追加

次に、`wrangler.toml` の `[vars]` セクションを更新してビルド用の環境変数を渡します。

```
[vars]
PKEY = "" # Private key for EOA wallet
CONTRACT_ADDRESS = "" # // Deployed ERC1155 or ERC721 contract address
PROJECT_ACCESS_KEY = "" # From sequence.build
CHAIN_HANDLE = "" # // Standardized chain name – See https://docs.sequence.xyz/multi-chain-support
```

#### Wrangler テンプレートで Window オブジェクトを実装する

なお、デプロイしようとすると、web3モジュールで必要な `window` オブジェクトが見つからないというエラーが発生します。

これを防ぐには、`wrangler.toml` ファイルに以下の行を追加し、環境を対応させてください。

```
...
node_compat = true # add this line
...
```

#### デプロイのテスト

`wrangler deploy` を使用して再デプロイできます。

次に、以下のように curl リクエストでエンドポイントをテストします。

```shell theme={null}
curl -X POST https://your-worker.your-subdomain.workers.dev \
-H "Content-Type: application/json" \
-d '{"proof": "<some_proof>", "address": "<some_address>", "tokenId": 0 }'

... invalid proof string ...

# and if you replace with actual proof (from a wallet client login) and address on polygon, it should return
success
```

[このdapp](https://sequence-ethauthproof-viewer.vercel.app/)を利用し、以下の手順でウォレットアドレスの証明を取得します。

#### ETHAuthProof Viewer dapp の利用

ページにアクセスしたら、最初にネットワークを選択してください。

次に、「接続してProof（証明）を生成」または「ローカルウォレットを生成」のいずれかを選択できます。

<Frame>
  <img src="https://mintcdn.com/sequence-0fb8d9e6/Kjh3D5BsUs2mHn5e/images/relayer/ethauthproof_viewer_connect.png?fit=max&auto=format&n=Kjh3D5BsUs2mHn5e&q=85&s=95e9330630ddd4f348d2bf5cf9bab90f" alt="ETHAuthProof Viewer" width="1422" height="714" data-path="images/relayer/ethauthproof_viewer_connect.png" />
</Frame>

`connect` ボタンを押し、その後 `copy to clipboard` をクリックしてください。

<Frame>
  <img src="https://mintcdn.com/sequence-0fb8d9e6/Kjh3D5BsUs2mHn5e/images/relayer/ethauthproof_viewer_copy.png?fit=max&auto=format&n=Kjh3D5BsUs2mHn5e&q=85&s=74f37584df3419fe95344c2dd1cd1c01" alt="ETHAuthProof Viewer copy to clipboard" width="1422" height="714" data-path="images/relayer/ethauthproof_viewer_copy.png" />
</Frame>

なお、この `ETHAuthProof` を他人と共有しないことをおすすめします。これを共有すると、他人があなたのウォレットの所有者であることを証明し、特定のAPIとやり取りできてしまいます。

最後に、`url`を[この手順](/guides/mint-collectibles-serverless#deploy-test)で取得したアプリのものに、`<some_proof>`をビューワーアプリからコピーした生成値に、`<some_address>`をご自身のウォレットアドレスに置き換えてください。これで、モックされた`0x`文字列が返されます。

```shell theme={null}
curl -X POST https://your-worker.your-subdomain.workers.dev \
-H "Content-Type: application/json" \
-d '{"proof": "<some_proof>", "address": "<some_address>", "tokenId": 0 }'
```

## 4. コレクティブルをウォレットにミントする

スポンサー付きコントラクトアドレスからコレクティブルをデプロイ・ミントするには、以下のパッケージをインストールします。

```shell theme={null}
pnpm install @0xsequence/auth ethers
```

そして、以前モックしていた `callContract` と `getBlockNumber` メソッドを次のように実装します：

```ts theme={null}
import { ethers } from 'ethers'
import { Session, SessionSettings } from '@0xsequence/auth'

...

const getBlockNumber = async (chainId: string, request: Request): Promise<number> => {
	const nodeUrl = `https://nodes.sequence.app/${chainId}`
	const provider = new ethers.providers.JsonRpcProvider({ url: nodeUrl, skipFetchSetup: true })
	return await provider.getBlockNumber()
}

const callContract = async (request: Request, env: Env, address: string, tokenId: number): Promise<ethers.providers.TransactionResponse> => {

	const nodeUrl = `https://nodes.sequence.app/${env.CHAIN_HANDLE}`
	const relayerUrl = `https://${env.CHAIN_HANDLE}-relayer.sequence.app`
	const contractAddress = env.CONTRACT_ADDRESS


	// instantiate settings
	const settings: Partial<SessionSettings> = {
		networks: [{
			...networks[findSupportedNetwork(env.CHAIN_HANDLE)!.chainId],
			rpcUrl: findSupportedNetwork(env.CHAIN_HANDLE)!.rpcUrl,
			relayer: {
				url: relayerUrl,
				provider: {
					url: findSupportedNetwork(env.CHAIN_HANDLE)!.rpcUrl
				}
			}
		}],
	}

    // create a single signer sequence wallet session
	const session = await Session.singleSigner({
		settings: settings,
		signer: env.PKEY,
		projectAccessKey: env.PROJECT_ACCESS_KEY
	})

	// get signer
	const signer = session.account.getSigner(findSupportedNetwork(env.CHAIN_HANDLE)!.chainId)

	// create interface from partial abi
	const collectibleInterface = new ethers.Interface([
		'function mint(address to, uint256 tokenId, uint256 amount, bytes data)'
	])

	// create calldata
	const data = collectibleInterface.encodeFunctionData(
		'mint', [address, tokenId, 1, "0x00"]
	)

	// create transaction object
	const txn = { to: contractAddress, data }

	try {
		return await signer.sendTransaction(txn)
	} catch (err) {
		throw err
	}
}
```

これらの手順が完了したら、[前の手順](/guides/mint-collectibles-serverless#testing)に従って再デプロイおよびテストできます。今回は、POSTリクエストでミント完了のトランザクションハッシュが返り、GETリクエストではブロック番号が返されるはずです。

全コードを確認したい場合は、[こちらの実装例をご覧ください](https://github.com/0xsequence-demos/cloudflare-worker-sequence-transactions-api)
