{
  "info": {
    "name": "Provably Fair Game Provider Sandbox",
    "description": "Casino partner sandbox flow for launch, session validation, casino balance, debit, credit, idempotency, rollback, round lookup, fairness verification, and reconciliation.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "baseUrl", "value": "http://localhost:3000" },
    { "key": "operatorId", "value": "demo" },
    { "key": "playerRef", "value": "player-demo-1" },
    { "key": "gameType", "value": "dice" },
    { "key": "launchMode", "value": "single" },
    { "key": "launchToken", "value": "" },
    { "key": "sessionId", "value": "" },
    { "key": "roundId", "value": "" },
    { "key": "failedRoundId", "value": "" },
    { "key": "serverSeedHash", "value": "" },
    { "key": "revealedServerSeed", "value": "" },
    { "key": "revealedServerSeedHash", "value": "" },
    { "key": "nonce", "value": "" },
    { "key": "clientSeed", "value": "partner-demo-client-seed" },
    { "key": "betIdempotencyKey", "value": "partner-demo-bet-001" },
    { "key": "failedBetIdempotencyKey", "value": "partner-demo-failed-round-001" }
  ],
  "item": [
    {
      "name": "Create Sandbox Launch",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('launch token returned', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.launchPayload.launchToken).to.be.a('string');",
              "  pm.collectionVariables.set('launchToken', data.launchPayload.launchToken);",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/sandbox/launch",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"operatorId\": \"{{operatorId}}\",\n  \"playerRef\": \"{{playerRef}}\",\n  \"gameType\": \"{{gameType}}\",\n  \"launchMode\": \"{{launchMode}}\"\n}"
        }
      }
    },
    {
      "name": "Create Session",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('session created and balance returned', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.session.sessionId).to.be.a('string');",
              "  pm.expect(data.balance.balance).to.be.a('number');",
              "  pm.collectionVariables.set('sessionId', data.session.sessionId);",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/api/sessions",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"operatorId\": \"{{operatorId}}\",\n  \"launchToken\": \"{{launchToken}}\",\n  \"gameType\": \"{{gameType}}\",\n  \"launchMode\": \"{{launchMode}}\"\n}"
        }
      }
    },
    {
      "name": "Get Balance",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('casino balance returned', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.balance).to.be.a('number');",
              "  pm.expect(data.currency).to.be.a('string');",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "url": "{{baseUrl}}/api/balance?sessionId={{sessionId}}"
      }
    },
    {
      "name": "Place Dice Bet",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('bet settled and proof stored', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.bet.roundId).to.be.a('string');",
              "  pm.expect(data.bet.status).to.eql('settled');",
              "  pm.collectionVariables.set('roundId', data.bet.roundId);",
              "  pm.collectionVariables.set('serverSeedHash', data.bet.proof.serverSeedHash);",
              "  pm.collectionVariables.set('nonce', String(data.bet.proof.nonce));",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/api/bets",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"sessionId\": \"{{sessionId}}\",\n  \"gameType\": \"dice\",\n  \"amount\": 10,\n  \"idempotencyKey\": \"{{betIdempotencyKey}}\",\n  \"clientSeed\": \"{{clientSeed}}\",\n  \"params\": {\n    \"target\": 50,\n    \"direction\": \"over\"\n  }\n}"
        }
      }
    },
    {
      "name": "Replay Dice Bet",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('identical retry returns the original round', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.bet.roundId).to.eql(pm.collectionVariables.get('roundId'));",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/api/bets",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"sessionId\": \"{{sessionId}}\",\n  \"gameType\": \"dice\",\n  \"amount\": 10,\n  \"idempotencyKey\": \"{{betIdempotencyKey}}\",\n  \"clientSeed\": \"{{clientSeed}}\",\n  \"params\": {\n    \"target\": 50,\n    \"direction\": \"over\"\n  }\n}"
        }
      }
    },
    {
      "name": "Idempotency Conflict",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('conflicting retry is rejected', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(pm.response.code).to.eql(400);",
              "  pm.expect(data.code).to.eql('IDEMPOTENCY_CONFLICT');",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/api/bets",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"sessionId\": \"{{sessionId}}\",\n  \"gameType\": \"dice\",\n  \"amount\": 11,\n  \"idempotencyKey\": \"{{betIdempotencyKey}}\",\n  \"clientSeed\": \"{{clientSeed}}\",\n  \"params\": {\n    \"target\": 50,\n    \"direction\": \"over\"\n  }\n}"
        }
      }
    },
    {
      "name": "Failed Round Rollback",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('failed round rolls back debit', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(pm.response.code).to.eql(400);",
              "  pm.expect(data.roundStatus).to.eql('rolled_back');",
              "  pm.collectionVariables.set('failedRoundId', data.roundId);",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/api/bets",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"sessionId\": \"{{sessionId}}\",\n  \"gameType\": \"dice\",\n  \"amount\": 9,\n  \"idempotencyKey\": \"{{failedBetIdempotencyKey}}\",\n  \"clientSeed\": \"partner-demo-failed-seed\",\n  \"params\": {\n    \"target\": 99,\n    \"direction\": \"over\"\n  }\n}"
        }
      }
    },
    {
      "name": "Round History",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('settled dispute packet returned', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.roundId).to.eql(pm.collectionVariables.get('roundId'));",
              "  pm.expect(data.casinoCallbackAudit.debit.path).to.eql('/wallet/debit');",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "url": "{{baseUrl}}/api/bets/{{roundId}}"
      }
    },
    {
      "name": "Failed Round History",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('rolled-back dispute packet returned', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.roundId).to.eql(pm.collectionVariables.get('failedRoundId'));",
              "  pm.expect(data.status).to.eql('rolled_back');",
              "  pm.expect(data.casinoCallbackAudit.rollback.path).to.eql('/wallet/rollback');",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "url": "{{baseUrl}}/api/bets/{{failedRoundId}}"
      }
    },
    {
      "name": "Reconciliation",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('reconciliation rows returned', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.length).to.be.greaterThan(0);",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "GET",
        "url": "{{baseUrl}}/api/reconciliation"
      }
    },
    {
      "name": "Rotate Seed",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('previous server seed revealed', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.revealed.serverSeed).to.be.a('string');",
              "  pm.collectionVariables.set('revealedServerSeed', data.revealed.serverSeed);",
              "  pm.collectionVariables.set('revealedServerSeedHash', data.revealed.serverSeedHash);",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/api/fairness/rotate",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"sessionId\": \"{{sessionId}}\"\n}"
        }
      }
    },
    {
      "name": "Verify Result",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('revealed seed matches proof hash', function () {",
              "  const data = pm.response.json();",
              "  pm.expect(data.serverSeedMatchesHash).to.eql(true);",
              "});"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "url": "{{baseUrl}}/api/verify",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"serverSeed\": \"{{revealedServerSeed}}\",\n  \"serverSeedHash\": \"{{serverSeedHash}}\",\n  \"clientSeed\": \"{{clientSeed}}\",\n  \"nonce\": {{nonce}},\n  \"gameType\": \"dice\",\n  \"amount\": 10,\n  \"params\": {\n    \"target\": 50,\n    \"direction\": \"over\"\n  }\n}"
        }
      }
    }
  ]
}
