본문으로 건너뛰기

Transfer

After connecting a wallet, build a PTB that transfers SUI to the to address and execute it with useSignAndExecuteTransaction.

This example uses devnet.

라이브 에디터
function Transfer() {
  const account = useCurrentAccount();
  const { mutate: signAndExecuteTransaction, isPending } = useSignAndExecuteTransaction();
  const { data: balance } = useSuiClientQuery('getBalance', { owner: account?.address ?? '' }, { enabled: !!account });

  const [to, setTo] = useState('');
  const [mist, setMist] = useState('1000000');
  const [digest, setDigest] = useState('');
  const [error, setError] = useState('');

  if (!account) {
    return (
      <div className="alert alert--info">
        Connect your wallet first (top-right wallet icon).
      </div>
    );
  }

  const hasValidTo = /^0x[0-9a-fA-F]+$/.test(to) && to.length >= 4;
  const hasValidAmount = /^[0-9]+$/.test(mist) && mist !== '0';
  const canSubmit = hasValidTo && hasValidAmount && !isPending;

  return (
    <div style={{ maxWidth: 980 }}>
      <div style={{ marginBottom: 10, fontSize: 12, fontWeight: 700, color: 'var(--ifm-color-emphasis-700)' }}>
        General
      </div>

      <div
        style={{
          border: '1px solid var(--ifm-color-emphasis-300)',
          borderRadius: 12,
          padding: 16,
          background: 'var(--ifm-background-surface-color)',
        }}
      >
        <div className="row" style={{ rowGap: 12 }}>
          <div className="col col--3">
            <div className="text--muted" style={{ fontSize: 12 }}>
              Network
            </div>
            <div style={{ fontWeight: 700 }}>
              <code>sui:devnet</code>
            </div>
          </div>

          <div className="col col--3">
            <div className="text--muted" style={{ fontSize: 12 }}>
              Balance
            </div>
            <div style={{ fontWeight: 700 }}>
              {balance ? (
                <>
                  <code>{balance.totalBalance}</code> <span className="text--muted">mist</span>
                </>
              ) : (
                <span className="text--muted">Loading…</span>
              )}
            </div>
          </div>

          <div className="col col--6">
            <div className="text--muted" style={{ fontSize: 12 }}>
              From
            </div>
            <div style={{ wordBreak: 'break-all' }}>
              <code>{account.address}</code>
            </div>
          </div>
        </div>
      </div>

      <div className="margin-top--lg" style={{ marginBottom: 10, fontSize: 12, fontWeight: 700, color: 'var(--ifm-color-emphasis-700)' }}>
        Transaction Inputs
      </div>

      <div
        style={{
          border: '1px solid var(--ifm-color-emphasis-300)',
          borderRadius: 12,
          padding: 16,
          background: 'var(--ifm-background-surface-color)',
        }}
      >
        <div className="row" style={{ rowGap: 16 }}>
          <div className="col col--8">
            <label className="form-label" style={{ display: 'block' }}>
              <b>To address</b>
            </label>
            <input
              className="input input--lg"
              value={to}
              onChange={(e) => setTo(e.target.value.trim())}
              placeholder="0x..."
            />
            {!to ? null : hasValidTo ? null : (
              <div className="margin-top--xs text--danger" style={{ fontSize: 13 }}>
                Enter a valid 0x address.
              </div>
            )}
          </div>

          <div className="col col--4">
            <label className="form-label" style={{ display: 'block' }}>
              <b>Amount (mist)</b>
            </label>
            <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
              <input
                className="input input--lg"
                value={mist}
                onChange={(e) => setMist(e.target.value.trim())}
                inputMode="numeric"
              />
              <button
                type="button"
                className="button button--secondary button--sm"
                onClick={() => {
                  if (!balance) return;
                  setMist(String(balance.totalBalance));
                }}
                disabled={!balance}
              >
                Max
              </button>
            </div>
            {!mist ? null : hasValidAmount ? null : (
              <div className="margin-top--xs text--danger" style={{ fontSize: 13 }}>
                Amount must be a positive integer.
              </div>
            )}
          </div>
        </div>

        <div className="margin-top--lg" style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
          <button
            className="button button--warning button--lg"
            disabled={!canSubmit}
            onClick={() => {
              setError('');
              setDigest('');

              if (!hasValidTo) return;
              if (!hasValidAmount) return;

              const tx = new Transaction();
              const [coin] = tx.splitCoins(tx.gas, [mist]);
              tx.transferObjects([coin], to);

              signAndExecuteTransaction(
                { transaction: tx, chain: 'sui:devnet' },
                {
                  onSuccess: (result) => setDigest(result.digest),
                  onError: (e) => {
                    const message = e?.message ?? String(e);
                    if (message.includes('No valid gas coins found')) {
                      setError(
                        'No gas coins found on Devnet for this address. Switch your wallet network to Devnet and request faucet SUI, then try again.',
                      );
                      return;
                    }
                    setError(message);
                  },
                },
              );
            }}
          >
            {isPending ? 'Executing…' : 'Execute'}
          </button>

          <div style={{ flex: 1, minWidth: 260 }}>
            <div className="text--muted" style={{ fontSize: 12 }}>
              Result
            </div>
            {digest ? (
              <div style={{ marginTop: 4, display: 'flex', gap: 10, alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap' }}>
                <code style={{ wordBreak: 'break-all' }}>{digest}</code>
                <a
                  href={`https://suiexplorer.com/txblock/${digest}?network=devnet`}
                  target="_blank"
                  rel="noreferrer"
                  style={{ fontWeight: 600, whiteSpace: 'nowrap' }}
                >
                  Open
                </a>
              </div>
            ) : (
              <div className="text--muted" style={{ fontSize: 13, marginTop: 4 }}>
                Result will appear here after execution.
              </div>
            )}
          </div>
        </div>
      </div>

      {error ? <div className="alert alert--danger margin-top--md">{error}</div> : null}
    </div>
  );
}

render(<Transfer />);
결과
Loading...